1//===--- CommentToXML.cpp - Convert comments to XML representation --------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "clang/Index/CommentToXML.h"
11#include "SimpleFormatContext.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/Attr.h"
14#include "clang/AST/Comment.h"
15#include "clang/AST/CommentVisitor.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
22using namespace clang;
23using namespace clang::comments;
24using namespace clang::index;
25
26namespace {
27
28/// This comparison will sort parameters with valid index by index, then vararg
29/// parameters, and invalid (unresolved) parameters last.
30class ParamCommandCommentCompareIndex {
31public:
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.
57class TParamCommandCommentComparePosition {
58public:
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.
82struct 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
97FullCommentParts::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  std::stable_sort(Params.begin(), Params.end(),
194                   ParamCommandCommentCompareIndex());
195
196  std::stable_sort(TParams.begin(), TParams.end(),
197                   TParamCommandCommentComparePosition());
198}
199
200void printHTMLStartTagComment(const HTMLStartTagComment *C,
201                              llvm::raw_svector_ostream &Result) {
202  Result << "<" << C->getTagName();
203
204  if (C->getNumAttrs() != 0) {
205    for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) {
206      Result << " ";
207      const HTMLStartTagComment::Attribute &Attr = C->getAttr(i);
208      Result << Attr.Name;
209      if (!Attr.Value.empty())
210        Result << "=\"" << Attr.Value << "\"";
211    }
212  }
213
214  if (!C->isSelfClosing())
215    Result << ">";
216  else
217    Result << "/>";
218}
219
220class CommentASTToHTMLConverter :
221    public ConstCommentVisitor<CommentASTToHTMLConverter> {
222public:
223  /// \param Str accumulator for HTML.
224  CommentASTToHTMLConverter(const FullComment *FC,
225                            SmallVectorImpl<char> &Str,
226                            const CommandTraits &Traits) :
227      FC(FC), Result(Str), Traits(Traits)
228  { }
229
230  // Inline content.
231  void visitTextComment(const TextComment *C);
232  void visitInlineCommandComment(const InlineCommandComment *C);
233  void visitHTMLStartTagComment(const HTMLStartTagComment *C);
234  void visitHTMLEndTagComment(const HTMLEndTagComment *C);
235
236  // Block content.
237  void visitParagraphComment(const ParagraphComment *C);
238  void visitBlockCommandComment(const BlockCommandComment *C);
239  void visitParamCommandComment(const ParamCommandComment *C);
240  void visitTParamCommandComment(const TParamCommandComment *C);
241  void visitVerbatimBlockComment(const VerbatimBlockComment *C);
242  void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
243  void visitVerbatimLineComment(const VerbatimLineComment *C);
244
245  void visitFullComment(const FullComment *C);
246
247  // Helpers.
248
249  /// Convert a paragraph that is not a block by itself (an argument to some
250  /// command).
251  void visitNonStandaloneParagraphComment(const ParagraphComment *C);
252
253  void appendToResultWithHTMLEscaping(StringRef S);
254
255private:
256  const FullComment *FC;
257  /// Output stream for HTML.
258  llvm::raw_svector_ostream Result;
259
260  const CommandTraits &Traits;
261};
262} // end unnamed namespace
263
264void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
265  appendToResultWithHTMLEscaping(C->getText());
266}
267
268void CommentASTToHTMLConverter::visitInlineCommandComment(
269                                  const InlineCommandComment *C) {
270  // Nothing to render if no arguments supplied.
271  if (C->getNumArgs() == 0)
272    return;
273
274  // Nothing to render if argument is empty.
275  StringRef Arg0 = C->getArgText(0);
276  if (Arg0.empty())
277    return;
278
279  switch (C->getRenderKind()) {
280  case InlineCommandComment::RenderNormal:
281    for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
282      appendToResultWithHTMLEscaping(C->getArgText(i));
283      Result << " ";
284    }
285    return;
286
287  case InlineCommandComment::RenderBold:
288    assert(C->getNumArgs() == 1);
289    Result << "<b>";
290    appendToResultWithHTMLEscaping(Arg0);
291    Result << "</b>";
292    return;
293  case InlineCommandComment::RenderMonospaced:
294    assert(C->getNumArgs() == 1);
295    Result << "<tt>";
296    appendToResultWithHTMLEscaping(Arg0);
297    Result<< "</tt>";
298    return;
299  case InlineCommandComment::RenderEmphasized:
300    assert(C->getNumArgs() == 1);
301    Result << "<em>";
302    appendToResultWithHTMLEscaping(Arg0);
303    Result << "</em>";
304    return;
305  }
306}
307
308void CommentASTToHTMLConverter::visitHTMLStartTagComment(
309                                  const HTMLStartTagComment *C) {
310  printHTMLStartTagComment(C, Result);
311}
312
313void CommentASTToHTMLConverter::visitHTMLEndTagComment(
314                                  const HTMLEndTagComment *C) {
315  Result << "</" << C->getTagName() << ">";
316}
317
318void CommentASTToHTMLConverter::visitParagraphComment(
319                                  const ParagraphComment *C) {
320  if (C->isWhitespace())
321    return;
322
323  Result << "<p>";
324  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
325       I != E; ++I) {
326    visit(*I);
327  }
328  Result << "</p>";
329}
330
331void CommentASTToHTMLConverter::visitBlockCommandComment(
332                                  const BlockCommandComment *C) {
333  const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID());
334  if (Info->IsBriefCommand) {
335    Result << "<p class=\"para-brief\">";
336    visitNonStandaloneParagraphComment(C->getParagraph());
337    Result << "</p>";
338    return;
339  }
340  if (Info->IsReturnsCommand) {
341    Result << "<p class=\"para-returns\">"
342              "<span class=\"word-returns\">Returns</span> ";
343    visitNonStandaloneParagraphComment(C->getParagraph());
344    Result << "</p>";
345    return;
346  }
347  // We don't know anything about this command.  Just render the paragraph.
348  visit(C->getParagraph());
349}
350
351void CommentASTToHTMLConverter::visitParamCommandComment(
352                                  const ParamCommandComment *C) {
353  if (C->isParamIndexValid()) {
354    if (C->isVarArgParam()) {
355      Result << "<dt class=\"param-name-index-vararg\">";
356      appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
357    } else {
358      Result << "<dt class=\"param-name-index-"
359             << C->getParamIndex()
360             << "\">";
361      appendToResultWithHTMLEscaping(C->getParamName(FC));
362    }
363  } else {
364    Result << "<dt class=\"param-name-index-invalid\">";
365    appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
366  }
367  Result << "</dt>";
368
369  if (C->isParamIndexValid()) {
370    if (C->isVarArgParam())
371      Result << "<dd class=\"param-descr-index-vararg\">";
372    else
373      Result << "<dd class=\"param-descr-index-"
374             << C->getParamIndex()
375             << "\">";
376  } else
377    Result << "<dd class=\"param-descr-index-invalid\">";
378
379  visitNonStandaloneParagraphComment(C->getParagraph());
380  Result << "</dd>";
381}
382
383void CommentASTToHTMLConverter::visitTParamCommandComment(
384                                  const TParamCommandComment *C) {
385  if (C->isPositionValid()) {
386    if (C->getDepth() == 1)
387      Result << "<dt class=\"tparam-name-index-"
388             << C->getIndex(0)
389             << "\">";
390    else
391      Result << "<dt class=\"tparam-name-index-other\">";
392    appendToResultWithHTMLEscaping(C->getParamName(FC));
393  } else {
394    Result << "<dt class=\"tparam-name-index-invalid\">";
395    appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
396  }
397
398  Result << "</dt>";
399
400  if (C->isPositionValid()) {
401    if (C->getDepth() == 1)
402      Result << "<dd class=\"tparam-descr-index-"
403             << C->getIndex(0)
404             << "\">";
405    else
406      Result << "<dd class=\"tparam-descr-index-other\">";
407  } else
408    Result << "<dd class=\"tparam-descr-index-invalid\">";
409
410  visitNonStandaloneParagraphComment(C->getParagraph());
411  Result << "</dd>";
412}
413
414void CommentASTToHTMLConverter::visitVerbatimBlockComment(
415                                  const VerbatimBlockComment *C) {
416  unsigned NumLines = C->getNumLines();
417  if (NumLines == 0)
418    return;
419
420  Result << "<pre>";
421  for (unsigned i = 0; i != NumLines; ++i) {
422    appendToResultWithHTMLEscaping(C->getText(i));
423    if (i + 1 != NumLines)
424      Result << '\n';
425  }
426  Result << "</pre>";
427}
428
429void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
430                                  const VerbatimBlockLineComment *C) {
431  llvm_unreachable("should not see this AST node");
432}
433
434void CommentASTToHTMLConverter::visitVerbatimLineComment(
435                                  const VerbatimLineComment *C) {
436  Result << "<pre>";
437  appendToResultWithHTMLEscaping(C->getText());
438  Result << "</pre>";
439}
440
441void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) {
442  FullCommentParts Parts(C, Traits);
443
444  bool FirstParagraphIsBrief = false;
445  if (Parts.Headerfile)
446    visit(Parts.Headerfile);
447  if (Parts.Brief)
448    visit(Parts.Brief);
449  else if (Parts.FirstParagraph) {
450    Result << "<p class=\"para-brief\">";
451    visitNonStandaloneParagraphComment(Parts.FirstParagraph);
452    Result << "</p>";
453    FirstParagraphIsBrief = true;
454  }
455
456  for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
457    const Comment *C = Parts.MiscBlocks[i];
458    if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
459      continue;
460    visit(C);
461  }
462
463  if (Parts.TParams.size() != 0) {
464    Result << "<dl>";
465    for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
466      visit(Parts.TParams[i]);
467    Result << "</dl>";
468  }
469
470  if (Parts.Params.size() != 0) {
471    Result << "<dl>";
472    for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
473      visit(Parts.Params[i]);
474    Result << "</dl>";
475  }
476
477  if (Parts.Returns.size() != 0) {
478    Result << "<div class=\"result-discussion\">";
479    for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
480      visit(Parts.Returns[i]);
481    Result << "</div>";
482  }
483
484  Result.flush();
485}
486
487void 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
498void 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
527namespace {
528class CommentASTToXMLConverter :
529    public ConstCommentVisitor<CommentASTToXMLConverter> {
530public:
531  /// \param Str accumulator for XML.
532  CommentASTToXMLConverter(const FullComment *FC,
533                           SmallVectorImpl<char> &Str,
534                           const CommandTraits &Traits,
535                           const SourceManager &SM,
536                           SimpleFormatContext &SFC,
537                           unsigned FUID) :
538      FC(FC), Result(Str), Traits(Traits), SM(SM),
539      FormatRewriterContext(SFC),
540      FormatInMemoryUniqueId(FUID) { }
541
542  // Inline content.
543  void visitTextComment(const TextComment *C);
544  void visitInlineCommandComment(const InlineCommandComment *C);
545  void visitHTMLStartTagComment(const HTMLStartTagComment *C);
546  void visitHTMLEndTagComment(const HTMLEndTagComment *C);
547
548  // Block content.
549  void visitParagraphComment(const ParagraphComment *C);
550
551  void appendParagraphCommentWithKind(const ParagraphComment *C,
552                                      StringRef Kind);
553
554  void visitBlockCommandComment(const BlockCommandComment *C);
555  void visitParamCommandComment(const ParamCommandComment *C);
556  void visitTParamCommandComment(const TParamCommandComment *C);
557  void visitVerbatimBlockComment(const VerbatimBlockComment *C);
558  void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
559  void visitVerbatimLineComment(const VerbatimLineComment *C);
560
561  void visitFullComment(const FullComment *C);
562
563  // Helpers.
564  void appendToResultWithXMLEscaping(StringRef S);
565  void appendToResultWithCDATAEscaping(StringRef S);
566
567  void formatTextOfDeclaration(const DeclInfo *DI,
568                               SmallString<128> &Declaration);
569
570private:
571  const FullComment *FC;
572
573  /// Output stream for XML.
574  llvm::raw_svector_ostream Result;
575
576  const CommandTraits &Traits;
577  const SourceManager &SM;
578  SimpleFormatContext &FormatRewriterContext;
579  unsigned FormatInMemoryUniqueId;
580};
581
582void getSourceTextOfDeclaration(const DeclInfo *ThisDecl,
583                                SmallVectorImpl<char> &Str) {
584  ASTContext &Context = ThisDecl->CurrentDecl->getASTContext();
585  const LangOptions &LangOpts = Context.getLangOpts();
586  llvm::raw_svector_ostream OS(Str);
587  PrintingPolicy PPolicy(LangOpts);
588  PPolicy.PolishForDeclaration = true;
589  PPolicy.TerseOutput = true;
590  ThisDecl->CurrentDecl->print(OS, PPolicy,
591                               /*Indentation*/0, /*PrintInstantiation*/false);
592}
593
594void CommentASTToXMLConverter::formatTextOfDeclaration(
595    const DeclInfo *DI, SmallString<128> &Declaration) {
596  // FIXME. formatting API expects null terminated input string.
597  // There might be more efficient way of doing this.
598  std::string StringDecl = Declaration.str();
599
600  // Formatter specific code.
601  // Form a unique in memory buffer name.
602  SmallString<128> filename;
603  filename += "xmldecl";
604  filename += llvm::utostr(FormatInMemoryUniqueId);
605  filename += ".xd";
606  FileID ID = FormatRewriterContext.createInMemoryFile(filename, StringDecl);
607  SourceLocation Start = FormatRewriterContext.Sources.getLocForStartOfFile(ID)
608      .getLocWithOffset(0);
609  unsigned Length = Declaration.size();
610
611  tooling::Replacements Replace = reformat(
612      format::getLLVMStyle(), FormatRewriterContext.Sources, ID,
613      CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length)));
614  applyAllReplacements(Replace, FormatRewriterContext.Rewrite);
615  Declaration = FormatRewriterContext.getRewrittenText(ID);
616}
617
618} // end unnamed namespace
619
620void CommentASTToXMLConverter::visitTextComment(const TextComment *C) {
621  appendToResultWithXMLEscaping(C->getText());
622}
623
624void CommentASTToXMLConverter::visitInlineCommandComment(
625    const InlineCommandComment *C) {
626  // Nothing to render if no arguments supplied.
627  if (C->getNumArgs() == 0)
628    return;
629
630  // Nothing to render if argument is empty.
631  StringRef Arg0 = C->getArgText(0);
632  if (Arg0.empty())
633    return;
634
635  switch (C->getRenderKind()) {
636  case InlineCommandComment::RenderNormal:
637    for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
638      appendToResultWithXMLEscaping(C->getArgText(i));
639      Result << " ";
640    }
641    return;
642  case InlineCommandComment::RenderBold:
643    assert(C->getNumArgs() == 1);
644    Result << "<bold>";
645    appendToResultWithXMLEscaping(Arg0);
646    Result << "</bold>";
647    return;
648  case InlineCommandComment::RenderMonospaced:
649    assert(C->getNumArgs() == 1);
650    Result << "<monospaced>";
651    appendToResultWithXMLEscaping(Arg0);
652    Result << "</monospaced>";
653    return;
654  case InlineCommandComment::RenderEmphasized:
655    assert(C->getNumArgs() == 1);
656    Result << "<emphasized>";
657    appendToResultWithXMLEscaping(Arg0);
658    Result << "</emphasized>";
659    return;
660  }
661}
662
663void CommentASTToXMLConverter::visitHTMLStartTagComment(
664    const HTMLStartTagComment *C) {
665  Result << "<rawHTML";
666  if (C->isMalformed())
667    Result << " isMalformed=\"1\"";
668  Result << ">";
669  {
670    SmallString<32> Tag;
671    {
672      llvm::raw_svector_ostream TagOS(Tag);
673      printHTMLStartTagComment(C, TagOS);
674    }
675    appendToResultWithCDATAEscaping(Tag);
676  }
677  Result << "</rawHTML>";
678}
679
680void
681CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) {
682  Result << "<rawHTML";
683  if (C->isMalformed())
684    Result << " isMalformed=\"1\"";
685  Result << ">&lt;/" << C->getTagName() << "&gt;</rawHTML>";
686}
687
688void
689CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) {
690  appendParagraphCommentWithKind(C, StringRef());
691}
692
693void CommentASTToXMLConverter::appendParagraphCommentWithKind(
694                                  const ParagraphComment *C,
695                                  StringRef ParagraphKind) {
696  if (C->isWhitespace())
697    return;
698
699  if (ParagraphKind.empty())
700    Result << "<Para>";
701  else
702    Result << "<Para kind=\"" << ParagraphKind << "\">";
703
704  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
705       I != E; ++I) {
706    visit(*I);
707  }
708  Result << "</Para>";
709}
710
711void CommentASTToXMLConverter::visitBlockCommandComment(
712    const BlockCommandComment *C) {
713  StringRef ParagraphKind;
714
715  switch (C->getCommandID()) {
716  case CommandTraits::KCI_attention:
717  case CommandTraits::KCI_author:
718  case CommandTraits::KCI_authors:
719  case CommandTraits::KCI_bug:
720  case CommandTraits::KCI_copyright:
721  case CommandTraits::KCI_date:
722  case CommandTraits::KCI_invariant:
723  case CommandTraits::KCI_note:
724  case CommandTraits::KCI_post:
725  case CommandTraits::KCI_pre:
726  case CommandTraits::KCI_remark:
727  case CommandTraits::KCI_remarks:
728  case CommandTraits::KCI_sa:
729  case CommandTraits::KCI_see:
730  case CommandTraits::KCI_since:
731  case CommandTraits::KCI_todo:
732  case CommandTraits::KCI_version:
733  case CommandTraits::KCI_warning:
734    ParagraphKind = C->getCommandName(Traits);
735  default:
736    break;
737  }
738
739  appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind);
740}
741
742void CommentASTToXMLConverter::visitParamCommandComment(
743    const ParamCommandComment *C) {
744  Result << "<Parameter><Name>";
745  appendToResultWithXMLEscaping(C->isParamIndexValid()
746                                    ? C->getParamName(FC)
747                                    : C->getParamNameAsWritten());
748  Result << "</Name>";
749
750  if (C->isParamIndexValid()) {
751    if (C->isVarArgParam())
752      Result << "<IsVarArg />";
753    else
754      Result << "<Index>" << C->getParamIndex() << "</Index>";
755  }
756
757  Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">";
758  switch (C->getDirection()) {
759  case ParamCommandComment::In:
760    Result << "in";
761    break;
762  case ParamCommandComment::Out:
763    Result << "out";
764    break;
765  case ParamCommandComment::InOut:
766    Result << "in,out";
767    break;
768  }
769  Result << "</Direction><Discussion>";
770  visit(C->getParagraph());
771  Result << "</Discussion></Parameter>";
772}
773
774void CommentASTToXMLConverter::visitTParamCommandComment(
775                                  const TParamCommandComment *C) {
776  Result << "<Parameter><Name>";
777  appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC)
778                                : C->getParamNameAsWritten());
779  Result << "</Name>";
780
781  if (C->isPositionValid() && C->getDepth() == 1) {
782    Result << "<Index>" << C->getIndex(0) << "</Index>";
783  }
784
785  Result << "<Discussion>";
786  visit(C->getParagraph());
787  Result << "</Discussion></Parameter>";
788}
789
790void CommentASTToXMLConverter::visitVerbatimBlockComment(
791                                  const VerbatimBlockComment *C) {
792  unsigned NumLines = C->getNumLines();
793  if (NumLines == 0)
794    return;
795
796  switch (C->getCommandID()) {
797  case CommandTraits::KCI_code:
798    Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">";
799    break;
800  default:
801    Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
802    break;
803  }
804  for (unsigned i = 0; i != NumLines; ++i) {
805    appendToResultWithXMLEscaping(C->getText(i));
806    if (i + 1 != NumLines)
807      Result << '\n';
808  }
809  Result << "</Verbatim>";
810}
811
812void CommentASTToXMLConverter::visitVerbatimBlockLineComment(
813                                  const VerbatimBlockLineComment *C) {
814  llvm_unreachable("should not see this AST node");
815}
816
817void CommentASTToXMLConverter::visitVerbatimLineComment(
818                                  const VerbatimLineComment *C) {
819  Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
820  appendToResultWithXMLEscaping(C->getText());
821  Result << "</Verbatim>";
822}
823
824void CommentASTToXMLConverter::visitFullComment(const FullComment *C) {
825  FullCommentParts Parts(C, Traits);
826
827  const DeclInfo *DI = C->getDeclInfo();
828  StringRef RootEndTag;
829  if (DI) {
830    switch (DI->getKind()) {
831    case DeclInfo::OtherKind:
832      RootEndTag = "</Other>";
833      Result << "<Other";
834      break;
835    case DeclInfo::FunctionKind:
836      RootEndTag = "</Function>";
837      Result << "<Function";
838      switch (DI->TemplateKind) {
839      case DeclInfo::NotTemplate:
840        break;
841      case DeclInfo::Template:
842        Result << " templateKind=\"template\"";
843        break;
844      case DeclInfo::TemplateSpecialization:
845        Result << " templateKind=\"specialization\"";
846        break;
847      case DeclInfo::TemplatePartialSpecialization:
848        llvm_unreachable("partial specializations of functions "
849                         "are not allowed in C++");
850      }
851      if (DI->IsInstanceMethod)
852        Result << " isInstanceMethod=\"1\"";
853      if (DI->IsClassMethod)
854        Result << " isClassMethod=\"1\"";
855      break;
856    case DeclInfo::ClassKind:
857      RootEndTag = "</Class>";
858      Result << "<Class";
859      switch (DI->TemplateKind) {
860      case DeclInfo::NotTemplate:
861        break;
862      case DeclInfo::Template:
863        Result << " templateKind=\"template\"";
864        break;
865      case DeclInfo::TemplateSpecialization:
866        Result << " templateKind=\"specialization\"";
867        break;
868      case DeclInfo::TemplatePartialSpecialization:
869        Result << " templateKind=\"partialSpecialization\"";
870        break;
871      }
872      break;
873    case DeclInfo::VariableKind:
874      RootEndTag = "</Variable>";
875      Result << "<Variable";
876      break;
877    case DeclInfo::NamespaceKind:
878      RootEndTag = "</Namespace>";
879      Result << "<Namespace";
880      break;
881    case DeclInfo::TypedefKind:
882      RootEndTag = "</Typedef>";
883      Result << "<Typedef";
884      break;
885    case DeclInfo::EnumKind:
886      RootEndTag = "</Enum>";
887      Result << "<Enum";
888      break;
889    }
890
891    {
892      // Print line and column number.
893      SourceLocation Loc = DI->CurrentDecl->getLocation();
894      std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
895      FileID FID = LocInfo.first;
896      unsigned FileOffset = LocInfo.second;
897
898      if (!FID.isInvalid()) {
899        if (const FileEntry *FE = SM.getFileEntryForID(FID)) {
900          Result << " file=\"";
901          appendToResultWithXMLEscaping(FE->getName());
902          Result << "\"";
903        }
904        Result << " line=\"" << SM.getLineNumber(FID, FileOffset)
905               << "\" column=\"" << SM.getColumnNumber(FID, FileOffset)
906               << "\"";
907      }
908    }
909
910    // Finish the root tag.
911    Result << ">";
912
913    bool FoundName = false;
914    if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) {
915      if (DeclarationName DeclName = ND->getDeclName()) {
916        Result << "<Name>";
917        std::string Name = DeclName.getAsString();
918        appendToResultWithXMLEscaping(Name);
919        FoundName = true;
920        Result << "</Name>";
921      }
922    }
923    if (!FoundName)
924      Result << "<Name>&lt;anonymous&gt;</Name>";
925
926    {
927      // Print USR.
928      SmallString<128> USR;
929      generateUSRForDecl(DI->CommentDecl, USR);
930      if (!USR.empty()) {
931        Result << "<USR>";
932        appendToResultWithXMLEscaping(USR);
933        Result << "</USR>";
934      }
935    }
936  } else {
937    // No DeclInfo -- just emit some root tag and name tag.
938    RootEndTag = "</Other>";
939    Result << "<Other><Name>unknown</Name>";
940  }
941
942  if (Parts.Headerfile) {
943    Result << "<Headerfile>";
944    visit(Parts.Headerfile);
945    Result << "</Headerfile>";
946  }
947
948  {
949    // Pretty-print the declaration.
950    Result << "<Declaration>";
951    SmallString<128> Declaration;
952    getSourceTextOfDeclaration(DI, Declaration);
953    formatTextOfDeclaration(DI, Declaration);
954    appendToResultWithXMLEscaping(Declaration);
955    Result << "</Declaration>";
956  }
957
958  bool FirstParagraphIsBrief = false;
959  if (Parts.Brief) {
960    Result << "<Abstract>";
961    visit(Parts.Brief);
962    Result << "</Abstract>";
963  } else if (Parts.FirstParagraph) {
964    Result << "<Abstract>";
965    visit(Parts.FirstParagraph);
966    Result << "</Abstract>";
967    FirstParagraphIsBrief = true;
968  }
969
970  if (Parts.TParams.size() != 0) {
971    Result << "<TemplateParameters>";
972    for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
973      visit(Parts.TParams[i]);
974    Result << "</TemplateParameters>";
975  }
976
977  if (Parts.Params.size() != 0) {
978    Result << "<Parameters>";
979    for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
980      visit(Parts.Params[i]);
981    Result << "</Parameters>";
982  }
983
984  if (Parts.Exceptions.size() != 0) {
985    Result << "<Exceptions>";
986    for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i)
987      visit(Parts.Exceptions[i]);
988    Result << "</Exceptions>";
989  }
990
991  if (Parts.Returns.size() != 0) {
992    Result << "<ResultDiscussion>";
993    for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
994      visit(Parts.Returns[i]);
995    Result << "</ResultDiscussion>";
996  }
997
998  if (DI->CommentDecl->hasAttrs()) {
999    const AttrVec &Attrs = DI->CommentDecl->getAttrs();
1000    for (unsigned i = 0, e = Attrs.size(); i != e; i++) {
1001      const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]);
1002      if (!AA) {
1003        if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) {
1004          if (DA->getMessage().empty())
1005            Result << "<Deprecated/>";
1006          else {
1007            Result << "<Deprecated>";
1008            appendToResultWithXMLEscaping(DA->getMessage());
1009            Result << "</Deprecated>";
1010          }
1011        }
1012        else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) {
1013          if (UA->getMessage().empty())
1014            Result << "<Unavailable/>";
1015          else {
1016            Result << "<Unavailable>";
1017            appendToResultWithXMLEscaping(UA->getMessage());
1018            Result << "</Unavailable>";
1019          }
1020        }
1021        continue;
1022      }
1023
1024      // 'availability' attribute.
1025      Result << "<Availability";
1026      StringRef Distribution;
1027      if (AA->getPlatform()) {
1028        Distribution = AvailabilityAttr::getPrettyPlatformName(
1029                                        AA->getPlatform()->getName());
1030        if (Distribution.empty())
1031          Distribution = AA->getPlatform()->getName();
1032      }
1033      Result << " distribution=\"" << Distribution << "\">";
1034      VersionTuple IntroducedInVersion = AA->getIntroduced();
1035      if (!IntroducedInVersion.empty()) {
1036        Result << "<IntroducedInVersion>"
1037               << IntroducedInVersion.getAsString()
1038               << "</IntroducedInVersion>";
1039      }
1040      VersionTuple DeprecatedInVersion = AA->getDeprecated();
1041      if (!DeprecatedInVersion.empty()) {
1042        Result << "<DeprecatedInVersion>"
1043               << DeprecatedInVersion.getAsString()
1044               << "</DeprecatedInVersion>";
1045      }
1046      VersionTuple RemovedAfterVersion = AA->getObsoleted();
1047      if (!RemovedAfterVersion.empty()) {
1048        Result << "<RemovedAfterVersion>"
1049               << RemovedAfterVersion.getAsString()
1050               << "</RemovedAfterVersion>";
1051      }
1052      StringRef DeprecationSummary = AA->getMessage();
1053      if (!DeprecationSummary.empty()) {
1054        Result << "<DeprecationSummary>";
1055        appendToResultWithXMLEscaping(DeprecationSummary);
1056        Result << "</DeprecationSummary>";
1057      }
1058      if (AA->getUnavailable())
1059        Result << "<Unavailable/>";
1060      Result << "</Availability>";
1061    }
1062  }
1063
1064  {
1065    bool StartTagEmitted = false;
1066    for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
1067      const Comment *C = Parts.MiscBlocks[i];
1068      if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
1069        continue;
1070      if (!StartTagEmitted) {
1071        Result << "<Discussion>";
1072        StartTagEmitted = true;
1073      }
1074      visit(C);
1075    }
1076    if (StartTagEmitted)
1077      Result << "</Discussion>";
1078  }
1079
1080  Result << RootEndTag;
1081
1082  Result.flush();
1083}
1084
1085void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) {
1086  for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
1087    const char C = *I;
1088    switch (C) {
1089    case '&':
1090      Result << "&amp;";
1091      break;
1092    case '<':
1093      Result << "&lt;";
1094      break;
1095    case '>':
1096      Result << "&gt;";
1097      break;
1098    case '"':
1099      Result << "&quot;";
1100      break;
1101    case '\'':
1102      Result << "&apos;";
1103      break;
1104    default:
1105      Result << C;
1106      break;
1107    }
1108  }
1109}
1110
1111void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) {
1112  if (S.empty())
1113    return;
1114
1115  Result << "<![CDATA[";
1116  while (!S.empty()) {
1117    size_t Pos = S.find("]]>");
1118    if (Pos == 0) {
1119      Result << "]]]]><![CDATA[>";
1120      S = S.drop_front(3);
1121      continue;
1122    }
1123    if (Pos == StringRef::npos)
1124      Pos = S.size();
1125
1126    Result << S.substr(0, Pos);
1127
1128    S = S.drop_front(Pos);
1129  }
1130  Result << "]]>";
1131}
1132
1133CommentToXMLConverter::CommentToXMLConverter() : FormatInMemoryUniqueId(0) {}
1134CommentToXMLConverter::~CommentToXMLConverter() {}
1135
1136void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC,
1137                                                 SmallVectorImpl<char> &HTML,
1138                                                 const ASTContext &Context) {
1139  CommentASTToHTMLConverter Converter(FC, HTML,
1140                                      Context.getCommentCommandTraits());
1141  Converter.visit(FC);
1142}
1143
1144void CommentToXMLConverter::convertHTMLTagNodeToText(
1145    const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text,
1146    const ASTContext &Context) {
1147  CommentASTToHTMLConverter Converter(nullptr, Text,
1148                                      Context.getCommentCommandTraits());
1149  Converter.visit(HTC);
1150}
1151
1152void CommentToXMLConverter::convertCommentToXML(const FullComment *FC,
1153                                                SmallVectorImpl<char> &XML,
1154                                                const ASTContext &Context) {
1155  if (!FormatContext || (FormatInMemoryUniqueId % 1000) == 0) {
1156    // Create a new format context, or re-create it after some number of
1157    // iterations, so the buffers don't grow too large.
1158    FormatContext.reset(new SimpleFormatContext(Context.getLangOpts()));
1159  }
1160
1161  CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(),
1162                                     Context.getSourceManager(), *FormatContext,
1163                                     FormatInMemoryUniqueId++);
1164  Converter.visit(FC);
1165}
1166
1167