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