1 //===--- CommentToXML.cpp - Convert comments to XML representation --------===//
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 "clang/Index/CommentToXML.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/Attr.h"
12 #include "clang/AST/Comment.h"
13 #include "clang/AST/CommentVisitor.h"
14 #include "clang/Basic/FileManager.h"
15 #include "clang/Basic/SourceManager.h"
16 #include "clang/Format/Format.h"
17 #include "clang/Index/USRGeneration.h"
18 #include "llvm/ADT/StringExtras.h"
19 #include "llvm/ADT/TinyPtrVector.h"
20 #include "llvm/Support/raw_ostream.h"
21 
22 using namespace clang;
23 using namespace clang::comments;
24 using namespace clang::index;
25 
26 namespace {
27 
28 /// This comparison will sort parameters with valid index by index, then vararg
29 /// parameters, and invalid (unresolved) parameters last.
30 class ParamCommandCommentCompareIndex {
31 public:
32   bool operator()(const ParamCommandComment *LHS,
33                   const ParamCommandComment *RHS) const {
34     unsigned LHSIndex = UINT_MAX;
35     unsigned RHSIndex = UINT_MAX;
36 
37     if (LHS->isParamIndexValid()) {
38       if (LHS->isVarArgParam())
39         LHSIndex = UINT_MAX - 1;
40       else
41         LHSIndex = LHS->getParamIndex();
42     }
43     if (RHS->isParamIndexValid()) {
44       if (RHS->isVarArgParam())
45         RHSIndex = UINT_MAX - 1;
46       else
47         RHSIndex = RHS->getParamIndex();
48     }
49     return LHSIndex < RHSIndex;
50   }
51 };
52 
53 /// This comparison will sort template parameters in the following order:
54 /// \li real template parameters (depth = 1) in index order;
55 /// \li all other names (depth > 1);
56 /// \li unresolved names.
57 class TParamCommandCommentComparePosition {
58 public:
59   bool operator()(const TParamCommandComment *LHS,
60                   const TParamCommandComment *RHS) const {
61     // Sort unresolved names last.
62     if (!LHS->isPositionValid())
63       return false;
64     if (!RHS->isPositionValid())
65       return true;
66 
67     if (LHS->getDepth() > 1)
68       return false;
69     if (RHS->getDepth() > 1)
70       return true;
71 
72     // Sort template parameters in index order.
73     if (LHS->getDepth() == 1 && RHS->getDepth() == 1)
74       return LHS->getIndex(0) < RHS->getIndex(0);
75 
76     // Leave all other names in source order.
77     return true;
78   }
79 };
80 
81 /// Separate parts of a FullComment.
82 struct FullCommentParts {
83   /// Take a full comment apart and initialize members accordingly.
84   FullCommentParts(const FullComment *C,
85                    const CommandTraits &Traits);
86 
87   const BlockContentComment *Brief;
88   const BlockContentComment *Headerfile;
89   const ParagraphComment *FirstParagraph;
90   SmallVector<const BlockCommandComment *, 4> Returns;
91   SmallVector<const ParamCommandComment *, 8> Params;
92   SmallVector<const TParamCommandComment *, 4> TParams;
93   llvm::TinyPtrVector<const BlockCommandComment *> Exceptions;
94   SmallVector<const BlockContentComment *, 8> MiscBlocks;
95 };
96 
97 FullCommentParts::FullCommentParts(const FullComment *C,
98                                    const CommandTraits &Traits) :
99     Brief(nullptr), Headerfile(nullptr), FirstParagraph(nullptr) {
100   for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
101        I != E; ++I) {
102     const Comment *Child = *I;
103     if (!Child)
104       continue;
105     switch (Child->getCommentKind()) {
106     case Comment::NoCommentKind:
107       continue;
108 
109     case Comment::ParagraphCommentKind: {
110       const ParagraphComment *PC = cast<ParagraphComment>(Child);
111       if (PC->isWhitespace())
112         break;
113       if (!FirstParagraph)
114         FirstParagraph = PC;
115 
116       MiscBlocks.push_back(PC);
117       break;
118     }
119 
120     case Comment::BlockCommandCommentKind: {
121       const BlockCommandComment *BCC = cast<BlockCommandComment>(Child);
122       const CommandInfo *Info = Traits.getCommandInfo(BCC->getCommandID());
123       if (!Brief && Info->IsBriefCommand) {
124         Brief = BCC;
125         break;
126       }
127       if (!Headerfile && Info->IsHeaderfileCommand) {
128         Headerfile = BCC;
129         break;
130       }
131       if (Info->IsReturnsCommand) {
132         Returns.push_back(BCC);
133         break;
134       }
135       if (Info->IsThrowsCommand) {
136         Exceptions.push_back(BCC);
137         break;
138       }
139       MiscBlocks.push_back(BCC);
140       break;
141     }
142 
143     case Comment::ParamCommandCommentKind: {
144       const ParamCommandComment *PCC = cast<ParamCommandComment>(Child);
145       if (!PCC->hasParamName())
146         break;
147 
148       if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph())
149         break;
150 
151       Params.push_back(PCC);
152       break;
153     }
154 
155     case Comment::TParamCommandCommentKind: {
156       const TParamCommandComment *TPCC = cast<TParamCommandComment>(Child);
157       if (!TPCC->hasParamName())
158         break;
159 
160       if (!TPCC->hasNonWhitespaceParagraph())
161         break;
162 
163       TParams.push_back(TPCC);
164       break;
165     }
166 
167     case Comment::VerbatimBlockCommentKind:
168       MiscBlocks.push_back(cast<BlockCommandComment>(Child));
169       break;
170 
171     case Comment::VerbatimLineCommentKind: {
172       const VerbatimLineComment *VLC = cast<VerbatimLineComment>(Child);
173       const CommandInfo *Info = Traits.getCommandInfo(VLC->getCommandID());
174       if (!Info->IsDeclarationCommand)
175         MiscBlocks.push_back(VLC);
176       break;
177     }
178 
179     case Comment::TextCommentKind:
180     case Comment::InlineCommandCommentKind:
181     case Comment::HTMLStartTagCommentKind:
182     case Comment::HTMLEndTagCommentKind:
183     case Comment::VerbatimBlockLineCommentKind:
184     case Comment::FullCommentKind:
185       llvm_unreachable("AST node of this kind can't be a child of "
186                        "a FullComment");
187     }
188   }
189 
190   // Sort params in order they are declared in the function prototype.
191   // Unresolved parameters are put at the end of the list in the same order
192   // they were seen in the comment.
193   llvm::stable_sort(Params, ParamCommandCommentCompareIndex());
194   llvm::stable_sort(TParams, TParamCommandCommentComparePosition());
195 }
196 
197 void printHTMLStartTagComment(const HTMLStartTagComment *C,
198                               llvm::raw_svector_ostream &Result) {
199   Result << "<" << C->getTagName();
200 
201   if (C->getNumAttrs() != 0) {
202     for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) {
203       Result << " ";
204       const HTMLStartTagComment::Attribute &Attr = C->getAttr(i);
205       Result << Attr.Name;
206       if (!Attr.Value.empty())
207         Result << "=\"" << Attr.Value << "\"";
208     }
209   }
210 
211   if (!C->isSelfClosing())
212     Result << ">";
213   else
214     Result << "/>";
215 }
216 
217 class CommentASTToHTMLConverter :
218     public ConstCommentVisitor<CommentASTToHTMLConverter> {
219 public:
220   /// \param Str accumulator for HTML.
221   CommentASTToHTMLConverter(const FullComment *FC,
222                             SmallVectorImpl<char> &Str,
223                             const CommandTraits &Traits) :
224       FC(FC), Result(Str), Traits(Traits)
225   { }
226 
227   // Inline content.
228   void visitTextComment(const TextComment *C);
229   void visitInlineCommandComment(const InlineCommandComment *C);
230   void visitHTMLStartTagComment(const HTMLStartTagComment *C);
231   void visitHTMLEndTagComment(const HTMLEndTagComment *C);
232 
233   // Block content.
234   void visitParagraphComment(const ParagraphComment *C);
235   void visitBlockCommandComment(const BlockCommandComment *C);
236   void visitParamCommandComment(const ParamCommandComment *C);
237   void visitTParamCommandComment(const TParamCommandComment *C);
238   void visitVerbatimBlockComment(const VerbatimBlockComment *C);
239   void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
240   void visitVerbatimLineComment(const VerbatimLineComment *C);
241 
242   void visitFullComment(const FullComment *C);
243 
244   // Helpers.
245 
246   /// Convert a paragraph that is not a block by itself (an argument to some
247   /// command).
248   void visitNonStandaloneParagraphComment(const ParagraphComment *C);
249 
250   void appendToResultWithHTMLEscaping(StringRef S);
251 
252 private:
253   const FullComment *FC;
254   /// Output stream for HTML.
255   llvm::raw_svector_ostream Result;
256 
257   const CommandTraits &Traits;
258 };
259 } // end unnamed namespace
260 
261 void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
262   appendToResultWithHTMLEscaping(C->getText());
263 }
264 
265 void CommentASTToHTMLConverter::visitInlineCommandComment(
266                                   const InlineCommandComment *C) {
267   // Nothing to render if no arguments supplied.
268   if (C->getNumArgs() == 0)
269     return;
270 
271   // Nothing to render if argument is empty.
272   StringRef Arg0 = C->getArgText(0);
273   if (Arg0.empty())
274     return;
275 
276   switch (C->getRenderKind()) {
277   case InlineCommandComment::RenderNormal:
278     for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
279       appendToResultWithHTMLEscaping(C->getArgText(i));
280       Result << " ";
281     }
282     return;
283 
284   case InlineCommandComment::RenderBold:
285     assert(C->getNumArgs() == 1);
286     Result << "<b>";
287     appendToResultWithHTMLEscaping(Arg0);
288     Result << "</b>";
289     return;
290   case InlineCommandComment::RenderMonospaced:
291     assert(C->getNumArgs() == 1);
292     Result << "<tt>";
293     appendToResultWithHTMLEscaping(Arg0);
294     Result<< "</tt>";
295     return;
296   case InlineCommandComment::RenderEmphasized:
297     assert(C->getNumArgs() == 1);
298     Result << "<em>";
299     appendToResultWithHTMLEscaping(Arg0);
300     Result << "</em>";
301     return;
302   case InlineCommandComment::RenderAnchor:
303     assert(C->getNumArgs() == 1);
304     Result << "<span id=\"" << Arg0 << "\"></span>";
305     return;
306   }
307 }
308 
309 void CommentASTToHTMLConverter::visitHTMLStartTagComment(
310                                   const HTMLStartTagComment *C) {
311   printHTMLStartTagComment(C, Result);
312 }
313 
314 void CommentASTToHTMLConverter::visitHTMLEndTagComment(
315                                   const HTMLEndTagComment *C) {
316   Result << "</" << C->getTagName() << ">";
317 }
318 
319 void CommentASTToHTMLConverter::visitParagraphComment(
320                                   const ParagraphComment *C) {
321   if (C->isWhitespace())
322     return;
323 
324   Result << "<p>";
325   for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
326        I != E; ++I) {
327     visit(*I);
328   }
329   Result << "</p>";
330 }
331 
332 void CommentASTToHTMLConverter::visitBlockCommandComment(
333                                   const BlockCommandComment *C) {
334   const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID());
335   if (Info->IsBriefCommand) {
336     Result << "<p class=\"para-brief\">";
337     visitNonStandaloneParagraphComment(C->getParagraph());
338     Result << "</p>";
339     return;
340   }
341   if (Info->IsReturnsCommand) {
342     Result << "<p class=\"para-returns\">"
343               "<span class=\"word-returns\">Returns</span> ";
344     visitNonStandaloneParagraphComment(C->getParagraph());
345     Result << "</p>";
346     return;
347   }
348   // We don't know anything about this command.  Just render the paragraph.
349   visit(C->getParagraph());
350 }
351 
352 void CommentASTToHTMLConverter::visitParamCommandComment(
353                                   const ParamCommandComment *C) {
354   if (C->isParamIndexValid()) {
355     if (C->isVarArgParam()) {
356       Result << "<dt class=\"param-name-index-vararg\">";
357       appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
358     } else {
359       Result << "<dt class=\"param-name-index-"
360              << C->getParamIndex()
361              << "\">";
362       appendToResultWithHTMLEscaping(C->getParamName(FC));
363     }
364   } else {
365     Result << "<dt class=\"param-name-index-invalid\">";
366     appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
367   }
368   Result << "</dt>";
369 
370   if (C->isParamIndexValid()) {
371     if (C->isVarArgParam())
372       Result << "<dd class=\"param-descr-index-vararg\">";
373     else
374       Result << "<dd class=\"param-descr-index-"
375              << C->getParamIndex()
376              << "\">";
377   } else
378     Result << "<dd class=\"param-descr-index-invalid\">";
379 
380   visitNonStandaloneParagraphComment(C->getParagraph());
381   Result << "</dd>";
382 }
383 
384 void CommentASTToHTMLConverter::visitTParamCommandComment(
385                                   const TParamCommandComment *C) {
386   if (C->isPositionValid()) {
387     if (C->getDepth() == 1)
388       Result << "<dt class=\"tparam-name-index-"
389              << C->getIndex(0)
390              << "\">";
391     else
392       Result << "<dt class=\"tparam-name-index-other\">";
393     appendToResultWithHTMLEscaping(C->getParamName(FC));
394   } else {
395     Result << "<dt class=\"tparam-name-index-invalid\">";
396     appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
397   }
398 
399   Result << "</dt>";
400 
401   if (C->isPositionValid()) {
402     if (C->getDepth() == 1)
403       Result << "<dd class=\"tparam-descr-index-"
404              << C->getIndex(0)
405              << "\">";
406     else
407       Result << "<dd class=\"tparam-descr-index-other\">";
408   } else
409     Result << "<dd class=\"tparam-descr-index-invalid\">";
410 
411   visitNonStandaloneParagraphComment(C->getParagraph());
412   Result << "</dd>";
413 }
414 
415 void CommentASTToHTMLConverter::visitVerbatimBlockComment(
416                                   const VerbatimBlockComment *C) {
417   unsigned NumLines = C->getNumLines();
418   if (NumLines == 0)
419     return;
420 
421   Result << "<pre>";
422   for (unsigned i = 0; i != NumLines; ++i) {
423     appendToResultWithHTMLEscaping(C->getText(i));
424     if (i + 1 != NumLines)
425       Result << '\n';
426   }
427   Result << "</pre>";
428 }
429 
430 void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
431                                   const VerbatimBlockLineComment *C) {
432   llvm_unreachable("should not see this AST node");
433 }
434 
435 void CommentASTToHTMLConverter::visitVerbatimLineComment(
436                                   const VerbatimLineComment *C) {
437   Result << "<pre>";
438   appendToResultWithHTMLEscaping(C->getText());
439   Result << "</pre>";
440 }
441 
442 void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) {
443   FullCommentParts Parts(C, Traits);
444 
445   bool FirstParagraphIsBrief = false;
446   if (Parts.Headerfile)
447     visit(Parts.Headerfile);
448   if (Parts.Brief)
449     visit(Parts.Brief);
450   else if (Parts.FirstParagraph) {
451     Result << "<p class=\"para-brief\">";
452     visitNonStandaloneParagraphComment(Parts.FirstParagraph);
453     Result << "</p>";
454     FirstParagraphIsBrief = true;
455   }
456 
457   for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
458     const Comment *C = Parts.MiscBlocks[i];
459     if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
460       continue;
461     visit(C);
462   }
463 
464   if (Parts.TParams.size() != 0) {
465     Result << "<dl>";
466     for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
467       visit(Parts.TParams[i]);
468     Result << "</dl>";
469   }
470 
471   if (Parts.Params.size() != 0) {
472     Result << "<dl>";
473     for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
474       visit(Parts.Params[i]);
475     Result << "</dl>";
476   }
477 
478   if (Parts.Returns.size() != 0) {
479     Result << "<div class=\"result-discussion\">";
480     for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
481       visit(Parts.Returns[i]);
482     Result << "</div>";
483   }
484 
485 }
486 
487 void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment(
488                                   const ParagraphComment *C) {
489   if (!C)
490     return;
491 
492   for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
493        I != E; ++I) {
494     visit(*I);
495   }
496 }
497 
498 void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) {
499   for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
500     const char C = *I;
501     switch (C) {
502     case '&':
503       Result << "&amp;";
504       break;
505     case '<':
506       Result << "&lt;";
507       break;
508     case '>':
509       Result << "&gt;";
510       break;
511     case '"':
512       Result << "&quot;";
513       break;
514     case '\'':
515       Result << "&#39;";
516       break;
517     case '/':
518       Result << "&#47;";
519       break;
520     default:
521       Result << C;
522       break;
523     }
524   }
525 }
526 
527 namespace {
528 class CommentASTToXMLConverter :
529     public ConstCommentVisitor<CommentASTToXMLConverter> {
530 public:
531   /// \param Str accumulator for XML.
532   CommentASTToXMLConverter(const FullComment *FC,
533                            SmallVectorImpl<char> &Str,
534                            const CommandTraits &Traits,
535                            const SourceManager &SM) :
536       FC(FC), Result(Str), Traits(Traits), SM(SM) { }
537 
538   // Inline content.
539   void visitTextComment(const TextComment *C);
540   void visitInlineCommandComment(const InlineCommandComment *C);
541   void visitHTMLStartTagComment(const HTMLStartTagComment *C);
542   void visitHTMLEndTagComment(const HTMLEndTagComment *C);
543 
544   // Block content.
545   void visitParagraphComment(const ParagraphComment *C);
546 
547   void appendParagraphCommentWithKind(const ParagraphComment *C,
548                                       StringRef Kind);
549 
550   void visitBlockCommandComment(const BlockCommandComment *C);
551   void visitParamCommandComment(const ParamCommandComment *C);
552   void visitTParamCommandComment(const TParamCommandComment *C);
553   void visitVerbatimBlockComment(const VerbatimBlockComment *C);
554   void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
555   void visitVerbatimLineComment(const VerbatimLineComment *C);
556 
557   void visitFullComment(const FullComment *C);
558 
559   // Helpers.
560   void appendToResultWithXMLEscaping(StringRef S);
561   void appendToResultWithCDATAEscaping(StringRef S);
562 
563   void formatTextOfDeclaration(const DeclInfo *DI,
564                                SmallString<128> &Declaration);
565 
566 private:
567   const FullComment *FC;
568 
569   /// Output stream for XML.
570   llvm::raw_svector_ostream Result;
571 
572   const CommandTraits &Traits;
573   const SourceManager &SM;
574 };
575 
576 void getSourceTextOfDeclaration(const DeclInfo *ThisDecl,
577                                 SmallVectorImpl<char> &Str) {
578   ASTContext &Context = ThisDecl->CurrentDecl->getASTContext();
579   const LangOptions &LangOpts = Context.getLangOpts();
580   llvm::raw_svector_ostream OS(Str);
581   PrintingPolicy PPolicy(LangOpts);
582   PPolicy.PolishForDeclaration = true;
583   PPolicy.TerseOutput = true;
584   PPolicy.ConstantsAsWritten = true;
585   ThisDecl->CurrentDecl->print(OS, PPolicy,
586                                /*Indentation*/0, /*PrintInstantiation*/false);
587 }
588 
589 void CommentASTToXMLConverter::formatTextOfDeclaration(
590     const DeclInfo *DI, SmallString<128> &Declaration) {
591   // Formatting API expects null terminated input string.
592   StringRef StringDecl(Declaration.c_str(), Declaration.size());
593 
594   // Formatter specific code.
595   unsigned Offset = 0;
596   unsigned Length = Declaration.size();
597 
598   format::FormatStyle Style = format::getLLVMStyle();
599   Style.FixNamespaceComments = false;
600   tooling::Replacements Replaces =
601       reformat(Style, StringDecl, tooling::Range(Offset, Length), "xmldecl.xd");
602   auto FormattedStringDecl = applyAllReplacements(StringDecl, Replaces);
603   if (static_cast<bool>(FormattedStringDecl)) {
604     Declaration = *FormattedStringDecl;
605   }
606 }
607 
608 } // end unnamed namespace
609 
610 void CommentASTToXMLConverter::visitTextComment(const TextComment *C) {
611   appendToResultWithXMLEscaping(C->getText());
612 }
613 
614 void CommentASTToXMLConverter::visitInlineCommandComment(
615     const InlineCommandComment *C) {
616   // Nothing to render if no arguments supplied.
617   if (C->getNumArgs() == 0)
618     return;
619 
620   // Nothing to render if argument is empty.
621   StringRef Arg0 = C->getArgText(0);
622   if (Arg0.empty())
623     return;
624 
625   switch (C->getRenderKind()) {
626   case InlineCommandComment::RenderNormal:
627     for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
628       appendToResultWithXMLEscaping(C->getArgText(i));
629       Result << " ";
630     }
631     return;
632   case InlineCommandComment::RenderBold:
633     assert(C->getNumArgs() == 1);
634     Result << "<bold>";
635     appendToResultWithXMLEscaping(Arg0);
636     Result << "</bold>";
637     return;
638   case InlineCommandComment::RenderMonospaced:
639     assert(C->getNumArgs() == 1);
640     Result << "<monospaced>";
641     appendToResultWithXMLEscaping(Arg0);
642     Result << "</monospaced>";
643     return;
644   case InlineCommandComment::RenderEmphasized:
645     assert(C->getNumArgs() == 1);
646     Result << "<emphasized>";
647     appendToResultWithXMLEscaping(Arg0);
648     Result << "</emphasized>";
649     return;
650   case InlineCommandComment::RenderAnchor:
651     assert(C->getNumArgs() == 1);
652     Result << "<anchor id=\"" << Arg0 << "\"></anchor>";
653     return;
654   }
655 }
656 
657 void CommentASTToXMLConverter::visitHTMLStartTagComment(
658     const HTMLStartTagComment *C) {
659   Result << "<rawHTML";
660   if (C->isMalformed())
661     Result << " isMalformed=\"1\"";
662   Result << ">";
663   {
664     SmallString<32> Tag;
665     {
666       llvm::raw_svector_ostream TagOS(Tag);
667       printHTMLStartTagComment(C, TagOS);
668     }
669     appendToResultWithCDATAEscaping(Tag);
670   }
671   Result << "</rawHTML>";
672 }
673 
674 void
675 CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) {
676   Result << "<rawHTML";
677   if (C->isMalformed())
678     Result << " isMalformed=\"1\"";
679   Result << ">&lt;/" << C->getTagName() << "&gt;</rawHTML>";
680 }
681 
682 void
683 CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) {
684   appendParagraphCommentWithKind(C, StringRef());
685 }
686 
687 void CommentASTToXMLConverter::appendParagraphCommentWithKind(
688                                   const ParagraphComment *C,
689                                   StringRef ParagraphKind) {
690   if (C->isWhitespace())
691     return;
692 
693   if (ParagraphKind.empty())
694     Result << "<Para>";
695   else
696     Result << "<Para kind=\"" << ParagraphKind << "\">";
697 
698   for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
699        I != E; ++I) {
700     visit(*I);
701   }
702   Result << "</Para>";
703 }
704 
705 void CommentASTToXMLConverter::visitBlockCommandComment(
706     const BlockCommandComment *C) {
707   StringRef ParagraphKind;
708 
709   switch (C->getCommandID()) {
710   case CommandTraits::KCI_attention:
711   case CommandTraits::KCI_author:
712   case CommandTraits::KCI_authors:
713   case CommandTraits::KCI_bug:
714   case CommandTraits::KCI_copyright:
715   case CommandTraits::KCI_date:
716   case CommandTraits::KCI_invariant:
717   case CommandTraits::KCI_note:
718   case CommandTraits::KCI_post:
719   case CommandTraits::KCI_pre:
720   case CommandTraits::KCI_remark:
721   case CommandTraits::KCI_remarks:
722   case CommandTraits::KCI_sa:
723   case CommandTraits::KCI_see:
724   case CommandTraits::KCI_since:
725   case CommandTraits::KCI_todo:
726   case CommandTraits::KCI_version:
727   case CommandTraits::KCI_warning:
728     ParagraphKind = C->getCommandName(Traits);
729     break;
730   default:
731     break;
732   }
733 
734   appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind);
735 }
736 
737 void CommentASTToXMLConverter::visitParamCommandComment(
738     const ParamCommandComment *C) {
739   Result << "<Parameter><Name>";
740   appendToResultWithXMLEscaping(C->isParamIndexValid()
741                                     ? C->getParamName(FC)
742                                     : C->getParamNameAsWritten());
743   Result << "</Name>";
744 
745   if (C->isParamIndexValid()) {
746     if (C->isVarArgParam())
747       Result << "<IsVarArg />";
748     else
749       Result << "<Index>" << C->getParamIndex() << "</Index>";
750   }
751 
752   Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">";
753   switch (C->getDirection()) {
754   case ParamCommandComment::In:
755     Result << "in";
756     break;
757   case ParamCommandComment::Out:
758     Result << "out";
759     break;
760   case ParamCommandComment::InOut:
761     Result << "in,out";
762     break;
763   }
764   Result << "</Direction><Discussion>";
765   visit(C->getParagraph());
766   Result << "</Discussion></Parameter>";
767 }
768 
769 void CommentASTToXMLConverter::visitTParamCommandComment(
770                                   const TParamCommandComment *C) {
771   Result << "<Parameter><Name>";
772   appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC)
773                                 : C->getParamNameAsWritten());
774   Result << "</Name>";
775 
776   if (C->isPositionValid() && C->getDepth() == 1) {
777     Result << "<Index>" << C->getIndex(0) << "</Index>";
778   }
779 
780   Result << "<Discussion>";
781   visit(C->getParagraph());
782   Result << "</Discussion></Parameter>";
783 }
784 
785 void CommentASTToXMLConverter::visitVerbatimBlockComment(
786                                   const VerbatimBlockComment *C) {
787   unsigned NumLines = C->getNumLines();
788   if (NumLines == 0)
789     return;
790 
791   switch (C->getCommandID()) {
792   case CommandTraits::KCI_code:
793     Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">";
794     break;
795   default:
796     Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
797     break;
798   }
799   for (unsigned i = 0; i != NumLines; ++i) {
800     appendToResultWithXMLEscaping(C->getText(i));
801     if (i + 1 != NumLines)
802       Result << '\n';
803   }
804   Result << "</Verbatim>";
805 }
806 
807 void CommentASTToXMLConverter::visitVerbatimBlockLineComment(
808                                   const VerbatimBlockLineComment *C) {
809   llvm_unreachable("should not see this AST node");
810 }
811 
812 void CommentASTToXMLConverter::visitVerbatimLineComment(
813                                   const VerbatimLineComment *C) {
814   Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
815   appendToResultWithXMLEscaping(C->getText());
816   Result << "</Verbatim>";
817 }
818 
819 void CommentASTToXMLConverter::visitFullComment(const FullComment *C) {
820   FullCommentParts Parts(C, Traits);
821 
822   const DeclInfo *DI = C->getDeclInfo();
823   StringRef RootEndTag;
824   if (DI) {
825     switch (DI->getKind()) {
826     case DeclInfo::OtherKind:
827       RootEndTag = "</Other>";
828       Result << "<Other";
829       break;
830     case DeclInfo::FunctionKind:
831       RootEndTag = "</Function>";
832       Result << "<Function";
833       switch (DI->TemplateKind) {
834       case DeclInfo::NotTemplate:
835         break;
836       case DeclInfo::Template:
837         Result << " templateKind=\"template\"";
838         break;
839       case DeclInfo::TemplateSpecialization:
840         Result << " templateKind=\"specialization\"";
841         break;
842       case DeclInfo::TemplatePartialSpecialization:
843         llvm_unreachable("partial specializations of functions "
844                          "are not allowed in C++");
845       }
846       if (DI->IsInstanceMethod)
847         Result << " isInstanceMethod=\"1\"";
848       if (DI->IsClassMethod)
849         Result << " isClassMethod=\"1\"";
850       break;
851     case DeclInfo::ClassKind:
852       RootEndTag = "</Class>";
853       Result << "<Class";
854       switch (DI->TemplateKind) {
855       case DeclInfo::NotTemplate:
856         break;
857       case DeclInfo::Template:
858         Result << " templateKind=\"template\"";
859         break;
860       case DeclInfo::TemplateSpecialization:
861         Result << " templateKind=\"specialization\"";
862         break;
863       case DeclInfo::TemplatePartialSpecialization:
864         Result << " templateKind=\"partialSpecialization\"";
865         break;
866       }
867       break;
868     case DeclInfo::VariableKind:
869       RootEndTag = "</Variable>";
870       Result << "<Variable";
871       break;
872     case DeclInfo::NamespaceKind:
873       RootEndTag = "</Namespace>";
874       Result << "<Namespace";
875       break;
876     case DeclInfo::TypedefKind:
877       RootEndTag = "</Typedef>";
878       Result << "<Typedef";
879       break;
880     case DeclInfo::EnumKind:
881       RootEndTag = "</Enum>";
882       Result << "<Enum";
883       break;
884     }
885 
886     {
887       // Print line and column number.
888       SourceLocation Loc = DI->CurrentDecl->getLocation();
889       std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
890       FileID FID = LocInfo.first;
891       unsigned FileOffset = LocInfo.second;
892 
893       if (FID.isValid()) {
894         if (const FileEntry *FE = SM.getFileEntryForID(FID)) {
895           Result << " file=\"";
896           appendToResultWithXMLEscaping(FE->getName());
897           Result << "\"";
898         }
899         Result << " line=\"" << SM.getLineNumber(FID, FileOffset)
900                << "\" column=\"" << SM.getColumnNumber(FID, FileOffset)
901                << "\"";
902       }
903     }
904 
905     // Finish the root tag.
906     Result << ">";
907 
908     bool FoundName = false;
909     if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) {
910       if (DeclarationName DeclName = ND->getDeclName()) {
911         Result << "<Name>";
912         std::string Name = DeclName.getAsString();
913         appendToResultWithXMLEscaping(Name);
914         FoundName = true;
915         Result << "</Name>";
916       }
917     }
918     if (!FoundName)
919       Result << "<Name>&lt;anonymous&gt;</Name>";
920 
921     {
922       // Print USR.
923       SmallString<128> USR;
924       generateUSRForDecl(DI->CommentDecl, USR);
925       if (!USR.empty()) {
926         Result << "<USR>";
927         appendToResultWithXMLEscaping(USR);
928         Result << "</USR>";
929       }
930     }
931   } else {
932     // No DeclInfo -- just emit some root tag and name tag.
933     RootEndTag = "</Other>";
934     Result << "<Other><Name>unknown</Name>";
935   }
936 
937   if (Parts.Headerfile) {
938     Result << "<Headerfile>";
939     visit(Parts.Headerfile);
940     Result << "</Headerfile>";
941   }
942 
943   {
944     // Pretty-print the declaration.
945     Result << "<Declaration>";
946     SmallString<128> Declaration;
947     getSourceTextOfDeclaration(DI, Declaration);
948     formatTextOfDeclaration(DI, Declaration);
949     appendToResultWithXMLEscaping(Declaration);
950     Result << "</Declaration>";
951   }
952 
953   bool FirstParagraphIsBrief = false;
954   if (Parts.Brief) {
955     Result << "<Abstract>";
956     visit(Parts.Brief);
957     Result << "</Abstract>";
958   } else if (Parts.FirstParagraph) {
959     Result << "<Abstract>";
960     visit(Parts.FirstParagraph);
961     Result << "</Abstract>";
962     FirstParagraphIsBrief = true;
963   }
964 
965   if (Parts.TParams.size() != 0) {
966     Result << "<TemplateParameters>";
967     for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
968       visit(Parts.TParams[i]);
969     Result << "</TemplateParameters>";
970   }
971 
972   if (Parts.Params.size() != 0) {
973     Result << "<Parameters>";
974     for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
975       visit(Parts.Params[i]);
976     Result << "</Parameters>";
977   }
978 
979   if (Parts.Exceptions.size() != 0) {
980     Result << "<Exceptions>";
981     for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i)
982       visit(Parts.Exceptions[i]);
983     Result << "</Exceptions>";
984   }
985 
986   if (Parts.Returns.size() != 0) {
987     Result << "<ResultDiscussion>";
988     for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
989       visit(Parts.Returns[i]);
990     Result << "</ResultDiscussion>";
991   }
992 
993   if (DI->CommentDecl->hasAttrs()) {
994     const AttrVec &Attrs = DI->CommentDecl->getAttrs();
995     for (unsigned i = 0, e = Attrs.size(); i != e; i++) {
996       const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]);
997       if (!AA) {
998         if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) {
999           if (DA->getMessage().empty())
1000             Result << "<Deprecated/>";
1001           else {
1002             Result << "<Deprecated>";
1003             appendToResultWithXMLEscaping(DA->getMessage());
1004             Result << "</Deprecated>";
1005           }
1006         }
1007         else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) {
1008           if (UA->getMessage().empty())
1009             Result << "<Unavailable/>";
1010           else {
1011             Result << "<Unavailable>";
1012             appendToResultWithXMLEscaping(UA->getMessage());
1013             Result << "</Unavailable>";
1014           }
1015         }
1016         continue;
1017       }
1018 
1019       // 'availability' attribute.
1020       Result << "<Availability";
1021       StringRef Distribution;
1022       if (AA->getPlatform()) {
1023         Distribution = AvailabilityAttr::getPrettyPlatformName(
1024                                         AA->getPlatform()->getName());
1025         if (Distribution.empty())
1026           Distribution = AA->getPlatform()->getName();
1027       }
1028       Result << " distribution=\"" << Distribution << "\">";
1029       VersionTuple IntroducedInVersion = AA->getIntroduced();
1030       if (!IntroducedInVersion.empty()) {
1031         Result << "<IntroducedInVersion>"
1032                << IntroducedInVersion.getAsString()
1033                << "</IntroducedInVersion>";
1034       }
1035       VersionTuple DeprecatedInVersion = AA->getDeprecated();
1036       if (!DeprecatedInVersion.empty()) {
1037         Result << "<DeprecatedInVersion>"
1038                << DeprecatedInVersion.getAsString()
1039                << "</DeprecatedInVersion>";
1040       }
1041       VersionTuple RemovedAfterVersion = AA->getObsoleted();
1042       if (!RemovedAfterVersion.empty()) {
1043         Result << "<RemovedAfterVersion>"
1044                << RemovedAfterVersion.getAsString()
1045                << "</RemovedAfterVersion>";
1046       }
1047       StringRef DeprecationSummary = AA->getMessage();
1048       if (!DeprecationSummary.empty()) {
1049         Result << "<DeprecationSummary>";
1050         appendToResultWithXMLEscaping(DeprecationSummary);
1051         Result << "</DeprecationSummary>";
1052       }
1053       if (AA->getUnavailable())
1054         Result << "<Unavailable/>";
1055       Result << "</Availability>";
1056     }
1057   }
1058 
1059   {
1060     bool StartTagEmitted = false;
1061     for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
1062       const Comment *C = Parts.MiscBlocks[i];
1063       if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
1064         continue;
1065       if (!StartTagEmitted) {
1066         Result << "<Discussion>";
1067         StartTagEmitted = true;
1068       }
1069       visit(C);
1070     }
1071     if (StartTagEmitted)
1072       Result << "</Discussion>";
1073   }
1074 
1075   Result << RootEndTag;
1076 }
1077 
1078 void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) {
1079   for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
1080     const char C = *I;
1081     switch (C) {
1082     case '&':
1083       Result << "&amp;";
1084       break;
1085     case '<':
1086       Result << "&lt;";
1087       break;
1088     case '>':
1089       Result << "&gt;";
1090       break;
1091     case '"':
1092       Result << "&quot;";
1093       break;
1094     case '\'':
1095       Result << "&apos;";
1096       break;
1097     default:
1098       Result << C;
1099       break;
1100     }
1101   }
1102 }
1103 
1104 void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) {
1105   if (S.empty())
1106     return;
1107 
1108   Result << "<![CDATA[";
1109   while (!S.empty()) {
1110     size_t Pos = S.find("]]>");
1111     if (Pos == 0) {
1112       Result << "]]]]><![CDATA[>";
1113       S = S.drop_front(3);
1114       continue;
1115     }
1116     if (Pos == StringRef::npos)
1117       Pos = S.size();
1118 
1119     Result << S.substr(0, Pos);
1120 
1121     S = S.drop_front(Pos);
1122   }
1123   Result << "]]>";
1124 }
1125 
1126 CommentToXMLConverter::CommentToXMLConverter() {}
1127 CommentToXMLConverter::~CommentToXMLConverter() {}
1128 
1129 void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC,
1130                                                  SmallVectorImpl<char> &HTML,
1131                                                  const ASTContext &Context) {
1132   CommentASTToHTMLConverter Converter(FC, HTML,
1133                                       Context.getCommentCommandTraits());
1134   Converter.visit(FC);
1135 }
1136 
1137 void CommentToXMLConverter::convertHTMLTagNodeToText(
1138     const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text,
1139     const ASTContext &Context) {
1140   CommentASTToHTMLConverter Converter(nullptr, Text,
1141                                       Context.getCommentCommandTraits());
1142   Converter.visit(HTC);
1143 }
1144 
1145 void CommentToXMLConverter::convertCommentToXML(const FullComment *FC,
1146                                                 SmallVectorImpl<char> &XML,
1147                                                 const ASTContext &Context) {
1148   CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(),
1149                                      Context.getSourceManager());
1150   Converter.visit(FC);
1151 }
1152