CommentToXML.cpp revision 360784
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
20using namespace clang;
21using namespace clang::comments;
22using namespace clang::index;
23
24namespace {
25
26/// This comparison will sort parameters with valid index by index, then vararg
27/// parameters, and invalid (unresolved) parameters last.
28class ParamCommandCommentCompareIndex {
29public:
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.
55class TParamCommandCommentComparePosition {
56public:
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.
80struct 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
95FullCommentParts::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
195void 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
215class CommentASTToHTMLConverter :
216    public ConstCommentVisitor<CommentASTToHTMLConverter> {
217public:
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
250private:
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
259void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
260  appendToResultWithHTMLEscaping(C->getText());
261}
262
263void 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
307void CommentASTToHTMLConverter::visitHTMLStartTagComment(
308                                  const HTMLStartTagComment *C) {
309  printHTMLStartTagComment(C, Result);
310}
311
312void CommentASTToHTMLConverter::visitHTMLEndTagComment(
313                                  const HTMLEndTagComment *C) {
314  Result << "</" << C->getTagName() << ">";
315}
316
317void 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
330void 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
350void 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
382void 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
413void 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
428void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
429                                  const VerbatimBlockLineComment *C) {
430  llvm_unreachable("should not see this AST node");
431}
432
433void CommentASTToHTMLConverter::visitVerbatimLineComment(
434                                  const VerbatimLineComment *C) {
435  Result << "<pre>";
436  appendToResultWithHTMLEscaping(C->getText());
437  Result << "</pre>";
438}
439
440void 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
485void 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
496void 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
525namespace {
526class CommentASTToXMLConverter :
527    public ConstCommentVisitor<CommentASTToXMLConverter> {
528public:
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
564private:
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
574void 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
587void 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
608void CommentASTToXMLConverter::visitTextComment(const TextComment *C) {
609  appendToResultWithXMLEscaping(C->getText());
610}
611
612void 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
655void 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
672void
673CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) {
674  Result << "<rawHTML";
675  if (C->isMalformed())
676    Result << " isMalformed=\"1\"";
677  Result << ">&lt;/" << C->getTagName() << "&gt;</rawHTML>";
678}
679
680void
681CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) {
682  appendParagraphCommentWithKind(C, StringRef());
683}
684
685void 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
703void 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
735void 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
767void 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
783void 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
805void CommentASTToXMLConverter::visitVerbatimBlockLineComment(
806                                  const VerbatimBlockLineComment *C) {
807  llvm_unreachable("should not see this AST node");
808}
809
810void CommentASTToXMLConverter::visitVerbatimLineComment(
811                                  const VerbatimLineComment *C) {
812  Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
813  appendToResultWithXMLEscaping(C->getText());
814  Result << "</Verbatim>";
815}
816
817void 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
1076void 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
1102void 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
1124CommentToXMLConverter::CommentToXMLConverter() {}
1125CommentToXMLConverter::~CommentToXMLConverter() {}
1126
1127void 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
1135void 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
1143void 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