//===--- CommentToXML.cpp - Convert comments to XML representation --------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Index/CommentToXML.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/Comment.h" #include "clang/AST/CommentVisitor.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "clang/Format/Format.h" #include "clang/Index/USRGeneration.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/TinyPtrVector.h" #include "llvm/Support/raw_ostream.h" using namespace clang; using namespace clang::comments; using namespace clang::index; namespace { /// This comparison will sort parameters with valid index by index, then vararg /// parameters, and invalid (unresolved) parameters last. class ParamCommandCommentCompareIndex { public: bool operator()(const ParamCommandComment *LHS, const ParamCommandComment *RHS) const { unsigned LHSIndex = UINT_MAX; unsigned RHSIndex = UINT_MAX; if (LHS->isParamIndexValid()) { if (LHS->isVarArgParam()) LHSIndex = UINT_MAX - 1; else LHSIndex = LHS->getParamIndex(); } if (RHS->isParamIndexValid()) { if (RHS->isVarArgParam()) RHSIndex = UINT_MAX - 1; else RHSIndex = RHS->getParamIndex(); } return LHSIndex < RHSIndex; } }; /// This comparison will sort template parameters in the following order: /// \li real template parameters (depth = 1) in index order; /// \li all other names (depth > 1); /// \li unresolved names. class TParamCommandCommentComparePosition { public: bool operator()(const TParamCommandComment *LHS, const TParamCommandComment *RHS) const { // Sort unresolved names last. if (!LHS->isPositionValid()) return false; if (!RHS->isPositionValid()) return true; if (LHS->getDepth() > 1) return false; if (RHS->getDepth() > 1) return true; // Sort template parameters in index order. if (LHS->getDepth() == 1 && RHS->getDepth() == 1) return LHS->getIndex(0) < RHS->getIndex(0); // Leave all other names in source order. return true; } }; /// Separate parts of a FullComment. struct FullCommentParts { /// Take a full comment apart and initialize members accordingly. FullCommentParts(const FullComment *C, const CommandTraits &Traits); const BlockContentComment *Brief; const BlockContentComment *Headerfile; const ParagraphComment *FirstParagraph; SmallVector Returns; SmallVector Params; SmallVector TParams; llvm::TinyPtrVector Exceptions; SmallVector MiscBlocks; }; FullCommentParts::FullCommentParts(const FullComment *C, const CommandTraits &Traits) : Brief(nullptr), Headerfile(nullptr), FirstParagraph(nullptr) { for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; ++I) { const Comment *Child = *I; if (!Child) continue; switch (Child->getCommentKind()) { case CommentKind::None: continue; case CommentKind::ParagraphComment: { const ParagraphComment *PC = cast(Child); if (PC->isWhitespace()) break; if (!FirstParagraph) FirstParagraph = PC; MiscBlocks.push_back(PC); break; } case CommentKind::BlockCommandComment: { const BlockCommandComment *BCC = cast(Child); const CommandInfo *Info = Traits.getCommandInfo(BCC->getCommandID()); if (!Brief && Info->IsBriefCommand) { Brief = BCC; break; } if (!Headerfile && Info->IsHeaderfileCommand) { Headerfile = BCC; break; } if (Info->IsReturnsCommand) { Returns.push_back(BCC); break; } if (Info->IsThrowsCommand) { Exceptions.push_back(BCC); break; } MiscBlocks.push_back(BCC); break; } case CommentKind::ParamCommandComment: { const ParamCommandComment *PCC = cast(Child); if (!PCC->hasParamName()) break; if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph()) break; Params.push_back(PCC); break; } case CommentKind::TParamCommandComment: { const TParamCommandComment *TPCC = cast(Child); if (!TPCC->hasParamName()) break; if (!TPCC->hasNonWhitespaceParagraph()) break; TParams.push_back(TPCC); break; } case CommentKind::VerbatimBlockComment: MiscBlocks.push_back(cast(Child)); break; case CommentKind::VerbatimLineComment: { const VerbatimLineComment *VLC = cast(Child); const CommandInfo *Info = Traits.getCommandInfo(VLC->getCommandID()); if (!Info->IsDeclarationCommand) MiscBlocks.push_back(VLC); break; } case CommentKind::TextComment: case CommentKind::InlineCommandComment: case CommentKind::HTMLStartTagComment: case CommentKind::HTMLEndTagComment: case CommentKind::VerbatimBlockLineComment: case CommentKind::FullComment: llvm_unreachable("AST node of this kind can't be a child of " "a FullComment"); } } // Sort params in order they are declared in the function prototype. // Unresolved parameters are put at the end of the list in the same order // they were seen in the comment. llvm::stable_sort(Params, ParamCommandCommentCompareIndex()); llvm::stable_sort(TParams, TParamCommandCommentComparePosition()); } void printHTMLStartTagComment(const HTMLStartTagComment *C, llvm::raw_svector_ostream &Result) { Result << "<" << C->getTagName(); if (C->getNumAttrs() != 0) { for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) { Result << " "; const HTMLStartTagComment::Attribute &Attr = C->getAttr(i); Result << Attr.Name; if (!Attr.Value.empty()) Result << "=\"" << Attr.Value << "\""; } } if (!C->isSelfClosing()) Result << ">"; else Result << "/>"; } class CommentASTToHTMLConverter : public ConstCommentVisitor { public: /// \param Str accumulator for HTML. CommentASTToHTMLConverter(const FullComment *FC, SmallVectorImpl &Str, const CommandTraits &Traits) : FC(FC), Result(Str), Traits(Traits) { } // Inline content. void visitTextComment(const TextComment *C); void visitInlineCommandComment(const InlineCommandComment *C); void visitHTMLStartTagComment(const HTMLStartTagComment *C); void visitHTMLEndTagComment(const HTMLEndTagComment *C); // Block content. void visitParagraphComment(const ParagraphComment *C); void visitBlockCommandComment(const BlockCommandComment *C); void visitParamCommandComment(const ParamCommandComment *C); void visitTParamCommandComment(const TParamCommandComment *C); void visitVerbatimBlockComment(const VerbatimBlockComment *C); void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); void visitVerbatimLineComment(const VerbatimLineComment *C); void visitFullComment(const FullComment *C); // Helpers. /// Convert a paragraph that is not a block by itself (an argument to some /// command). void visitNonStandaloneParagraphComment(const ParagraphComment *C); void appendToResultWithHTMLEscaping(StringRef S); private: const FullComment *FC; /// Output stream for HTML. llvm::raw_svector_ostream Result; const CommandTraits &Traits; }; } // end unnamed namespace void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) { appendToResultWithHTMLEscaping(C->getText()); } void CommentASTToHTMLConverter::visitInlineCommandComment( const InlineCommandComment *C) { // Nothing to render if no arguments supplied. if (C->getNumArgs() == 0) return; // Nothing to render if argument is empty. StringRef Arg0 = C->getArgText(0); if (Arg0.empty()) return; switch (C->getRenderKind()) { case InlineCommandRenderKind::Normal: for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { appendToResultWithHTMLEscaping(C->getArgText(i)); Result << " "; } return; case InlineCommandRenderKind::Bold: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithHTMLEscaping(Arg0); Result << ""; return; case InlineCommandRenderKind::Monospaced: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithHTMLEscaping(Arg0); Result<< ""; return; case InlineCommandRenderKind::Emphasized: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithHTMLEscaping(Arg0); Result << ""; return; case InlineCommandRenderKind::Anchor: assert(C->getNumArgs() == 1); Result << ""; return; } } void CommentASTToHTMLConverter::visitHTMLStartTagComment( const HTMLStartTagComment *C) { printHTMLStartTagComment(C, Result); } void CommentASTToHTMLConverter::visitHTMLEndTagComment( const HTMLEndTagComment *C) { Result << "getTagName() << ">"; } void CommentASTToHTMLConverter::visitParagraphComment( const ParagraphComment *C) { if (C->isWhitespace()) return; Result << "

"; for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; ++I) { visit(*I); } Result << "

"; } void CommentASTToHTMLConverter::visitBlockCommandComment( const BlockCommandComment *C) { const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID()); if (Info->IsBriefCommand) { Result << "

"; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "

"; return; } if (Info->IsReturnsCommand) { Result << "

" "Returns "; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "

"; return; } // We don't know anything about this command. Just render the paragraph. visit(C->getParagraph()); } void CommentASTToHTMLConverter::visitParamCommandComment( const ParamCommandComment *C) { if (C->isParamIndexValid()) { if (C->isVarArgParam()) { Result << "
"; appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); } else { Result << "
getParamIndex() << "\">"; appendToResultWithHTMLEscaping(C->getParamName(FC)); } } else { Result << "
"; appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); } Result << "
"; if (C->isParamIndexValid()) { if (C->isVarArgParam()) Result << "
"; else Result << "
getParamIndex() << "\">"; } else Result << "
"; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "
"; } void CommentASTToHTMLConverter::visitTParamCommandComment( const TParamCommandComment *C) { if (C->isPositionValid()) { if (C->getDepth() == 1) Result << "
getIndex(0) << "\">"; else Result << "
"; appendToResultWithHTMLEscaping(C->getParamName(FC)); } else { Result << "
"; appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); } Result << "
"; if (C->isPositionValid()) { if (C->getDepth() == 1) Result << "
getIndex(0) << "\">"; else Result << "
"; } else Result << "
"; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "
"; } void CommentASTToHTMLConverter::visitVerbatimBlockComment( const VerbatimBlockComment *C) { unsigned NumLines = C->getNumLines(); if (NumLines == 0) return; Result << "
";
  for (unsigned i = 0; i != NumLines; ++i) {
    appendToResultWithHTMLEscaping(C->getText(i));
    if (i + 1 != NumLines)
      Result << '\n';
  }
  Result << "
"; } void CommentASTToHTMLConverter::visitVerbatimBlockLineComment( const VerbatimBlockLineComment *C) { llvm_unreachable("should not see this AST node"); } void CommentASTToHTMLConverter::visitVerbatimLineComment( const VerbatimLineComment *C) { Result << "
";
  appendToResultWithHTMLEscaping(C->getText());
  Result << "
"; } void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { FullCommentParts Parts(C, Traits); bool FirstParagraphIsBrief = false; if (Parts.Headerfile) visit(Parts.Headerfile); if (Parts.Brief) visit(Parts.Brief); else if (Parts.FirstParagraph) { Result << "

"; visitNonStandaloneParagraphComment(Parts.FirstParagraph); Result << "

"; FirstParagraphIsBrief = true; } for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { const Comment *C = Parts.MiscBlocks[i]; if (FirstParagraphIsBrief && C == Parts.FirstParagraph) continue; visit(C); } if (Parts.TParams.size() != 0) { Result << "
"; for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) visit(Parts.TParams[i]); Result << "
"; } if (Parts.Params.size() != 0) { Result << "
"; for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) visit(Parts.Params[i]); Result << "
"; } if (Parts.Returns.size() != 0) { Result << "
"; for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) visit(Parts.Returns[i]); Result << "
"; } } void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment( const ParagraphComment *C) { if (!C) return; for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; ++I) { visit(*I); } } void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) { for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { const char C = *I; switch (C) { case '&': Result << "&"; break; case '<': Result << "<"; break; case '>': Result << ">"; break; case '"': Result << """; break; case '\'': Result << "'"; break; case '/': Result << "/"; break; default: Result << C; break; } } } namespace { class CommentASTToXMLConverter : public ConstCommentVisitor { public: /// \param Str accumulator for XML. CommentASTToXMLConverter(const FullComment *FC, SmallVectorImpl &Str, const CommandTraits &Traits, const SourceManager &SM) : FC(FC), Result(Str), Traits(Traits), SM(SM) { } // Inline content. void visitTextComment(const TextComment *C); void visitInlineCommandComment(const InlineCommandComment *C); void visitHTMLStartTagComment(const HTMLStartTagComment *C); void visitHTMLEndTagComment(const HTMLEndTagComment *C); // Block content. void visitParagraphComment(const ParagraphComment *C); void appendParagraphCommentWithKind(const ParagraphComment *C, StringRef Kind); void visitBlockCommandComment(const BlockCommandComment *C); void visitParamCommandComment(const ParamCommandComment *C); void visitTParamCommandComment(const TParamCommandComment *C); void visitVerbatimBlockComment(const VerbatimBlockComment *C); void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); void visitVerbatimLineComment(const VerbatimLineComment *C); void visitFullComment(const FullComment *C); // Helpers. void appendToResultWithXMLEscaping(StringRef S); void appendToResultWithCDATAEscaping(StringRef S); void formatTextOfDeclaration(const DeclInfo *DI, SmallString<128> &Declaration); private: const FullComment *FC; /// Output stream for XML. llvm::raw_svector_ostream Result; const CommandTraits &Traits; const SourceManager &SM; }; void getSourceTextOfDeclaration(const DeclInfo *ThisDecl, SmallVectorImpl &Str) { ASTContext &Context = ThisDecl->CurrentDecl->getASTContext(); const LangOptions &LangOpts = Context.getLangOpts(); llvm::raw_svector_ostream OS(Str); PrintingPolicy PPolicy(LangOpts); PPolicy.PolishForDeclaration = true; PPolicy.TerseOutput = true; PPolicy.ConstantsAsWritten = true; ThisDecl->CurrentDecl->print(OS, PPolicy, /*Indentation*/0, /*PrintInstantiation*/false); } void CommentASTToXMLConverter::formatTextOfDeclaration( const DeclInfo *DI, SmallString<128> &Declaration) { // Formatting API expects null terminated input string. StringRef StringDecl(Declaration.c_str(), Declaration.size()); // Formatter specific code. unsigned Offset = 0; unsigned Length = Declaration.size(); format::FormatStyle Style = format::getLLVMStyle(); Style.FixNamespaceComments = false; tooling::Replacements Replaces = reformat(Style, StringDecl, tooling::Range(Offset, Length), "xmldecl.xd"); auto FormattedStringDecl = applyAllReplacements(StringDecl, Replaces); if (static_cast(FormattedStringDecl)) { Declaration = *FormattedStringDecl; } } } // end unnamed namespace void CommentASTToXMLConverter::visitTextComment(const TextComment *C) { appendToResultWithXMLEscaping(C->getText()); } void CommentASTToXMLConverter::visitInlineCommandComment( const InlineCommandComment *C) { // Nothing to render if no arguments supplied. if (C->getNumArgs() == 0) return; // Nothing to render if argument is empty. StringRef Arg0 = C->getArgText(0); if (Arg0.empty()) return; switch (C->getRenderKind()) { case InlineCommandRenderKind::Normal: for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { appendToResultWithXMLEscaping(C->getArgText(i)); Result << " "; } return; case InlineCommandRenderKind::Bold: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithXMLEscaping(Arg0); Result << ""; return; case InlineCommandRenderKind::Monospaced: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithXMLEscaping(Arg0); Result << ""; return; case InlineCommandRenderKind::Emphasized: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithXMLEscaping(Arg0); Result << ""; return; case InlineCommandRenderKind::Anchor: assert(C->getNumArgs() == 1); Result << ""; return; } } void CommentASTToXMLConverter::visitHTMLStartTagComment( const HTMLStartTagComment *C) { Result << "isMalformed()) Result << " isMalformed=\"1\""; Result << ">"; { SmallString<32> Tag; { llvm::raw_svector_ostream TagOS(Tag); printHTMLStartTagComment(C, TagOS); } appendToResultWithCDATAEscaping(Tag); } Result << ""; } void CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) { Result << "isMalformed()) Result << " isMalformed=\"1\""; Result << "></" << C->getTagName() << ">"; } void CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) { appendParagraphCommentWithKind(C, StringRef()); } void CommentASTToXMLConverter::appendParagraphCommentWithKind( const ParagraphComment *C, StringRef ParagraphKind) { if (C->isWhitespace()) return; if (ParagraphKind.empty()) Result << ""; else Result << ""; for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; ++I) { visit(*I); } Result << ""; } void CommentASTToXMLConverter::visitBlockCommandComment( const BlockCommandComment *C) { StringRef ParagraphKind; switch (C->getCommandID()) { case CommandTraits::KCI_attention: case CommandTraits::KCI_author: case CommandTraits::KCI_authors: case CommandTraits::KCI_bug: case CommandTraits::KCI_copyright: case CommandTraits::KCI_date: case CommandTraits::KCI_invariant: case CommandTraits::KCI_note: case CommandTraits::KCI_post: case CommandTraits::KCI_pre: case CommandTraits::KCI_remark: case CommandTraits::KCI_remarks: case CommandTraits::KCI_sa: case CommandTraits::KCI_see: case CommandTraits::KCI_since: case CommandTraits::KCI_todo: case CommandTraits::KCI_version: case CommandTraits::KCI_warning: ParagraphKind = C->getCommandName(Traits); break; default: break; } appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind); } void CommentASTToXMLConverter::visitParamCommandComment( const ParamCommandComment *C) { Result << ""; appendToResultWithXMLEscaping(C->isParamIndexValid() ? C->getParamName(FC) : C->getParamNameAsWritten()); Result << ""; if (C->isParamIndexValid()) { if (C->isVarArgParam()) Result << ""; else Result << "" << C->getParamIndex() << ""; } Result << "isDirectionExplicit() << "\">"; switch (C->getDirection()) { case ParamCommandPassDirection::In: Result << "in"; break; case ParamCommandPassDirection::Out: Result << "out"; break; case ParamCommandPassDirection::InOut: Result << "in,out"; break; } Result << ""; visit(C->getParagraph()); Result << ""; } void CommentASTToXMLConverter::visitTParamCommandComment( const TParamCommandComment *C) { Result << ""; appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC) : C->getParamNameAsWritten()); Result << ""; if (C->isPositionValid() && C->getDepth() == 1) { Result << "" << C->getIndex(0) << ""; } Result << ""; visit(C->getParagraph()); Result << ""; } void CommentASTToXMLConverter::visitVerbatimBlockComment( const VerbatimBlockComment *C) { unsigned NumLines = C->getNumLines(); if (NumLines == 0) return; switch (C->getCommandID()) { case CommandTraits::KCI_code: Result << ""; break; default: Result << ""; break; } for (unsigned i = 0; i != NumLines; ++i) { appendToResultWithXMLEscaping(C->getText(i)); if (i + 1 != NumLines) Result << '\n'; } Result << ""; } void CommentASTToXMLConverter::visitVerbatimBlockLineComment( const VerbatimBlockLineComment *C) { llvm_unreachable("should not see this AST node"); } void CommentASTToXMLConverter::visitVerbatimLineComment( const VerbatimLineComment *C) { Result << ""; appendToResultWithXMLEscaping(C->getText()); Result << ""; } void CommentASTToXMLConverter::visitFullComment(const FullComment *C) { FullCommentParts Parts(C, Traits); const DeclInfo *DI = C->getDeclInfo(); StringRef RootEndTag; if (DI) { switch (DI->getKind()) { case DeclInfo::OtherKind: RootEndTag = ""; Result << "TemplateKind) { case DeclInfo::NotTemplate: break; case DeclInfo::Template: Result << " templateKind=\"template\""; break; case DeclInfo::TemplateSpecialization: Result << " templateKind=\"specialization\""; break; case DeclInfo::TemplatePartialSpecialization: llvm_unreachable("partial specializations of functions " "are not allowed in C++"); } if (DI->IsInstanceMethod) Result << " isInstanceMethod=\"1\""; if (DI->IsClassMethod) Result << " isClassMethod=\"1\""; break; case DeclInfo::ClassKind: RootEndTag = ""; Result << "TemplateKind) { case DeclInfo::NotTemplate: break; case DeclInfo::Template: Result << " templateKind=\"template\""; break; case DeclInfo::TemplateSpecialization: Result << " templateKind=\"specialization\""; break; case DeclInfo::TemplatePartialSpecialization: Result << " templateKind=\"partialSpecialization\""; break; } break; case DeclInfo::VariableKind: RootEndTag = ""; Result << "CurrentDecl->getLocation(); std::pair LocInfo = SM.getDecomposedLoc(Loc); FileID FID = LocInfo.first; unsigned FileOffset = LocInfo.second; if (FID.isValid()) { if (OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID)) { Result << " file=\""; appendToResultWithXMLEscaping(FE->getName()); Result << "\""; } Result << " line=\"" << SM.getLineNumber(FID, FileOffset) << "\" column=\"" << SM.getColumnNumber(FID, FileOffset) << "\""; } } // Finish the root tag. Result << ">"; bool FoundName = false; if (const NamedDecl *ND = dyn_cast(DI->CommentDecl)) { if (DeclarationName DeclName = ND->getDeclName()) { Result << ""; std::string Name = DeclName.getAsString(); appendToResultWithXMLEscaping(Name); FoundName = true; Result << ""; } } if (!FoundName) Result << "<anonymous>"; { // Print USR. SmallString<128> USR; generateUSRForDecl(DI->CommentDecl, USR); if (!USR.empty()) { Result << ""; appendToResultWithXMLEscaping(USR); Result << ""; } } } else { // No DeclInfo -- just emit some root tag and name tag. RootEndTag = ""; Result << "unknown"; } if (Parts.Headerfile) { Result << ""; visit(Parts.Headerfile); Result << ""; } { // Pretty-print the declaration. Result << ""; SmallString<128> Declaration; getSourceTextOfDeclaration(DI, Declaration); formatTextOfDeclaration(DI, Declaration); appendToResultWithXMLEscaping(Declaration); Result << ""; } bool FirstParagraphIsBrief = false; if (Parts.Brief) { Result << ""; visit(Parts.Brief); Result << ""; } else if (Parts.FirstParagraph) { Result << ""; visit(Parts.FirstParagraph); Result << ""; FirstParagraphIsBrief = true; } if (Parts.TParams.size() != 0) { Result << ""; for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) visit(Parts.TParams[i]); Result << ""; } if (Parts.Params.size() != 0) { Result << ""; for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) visit(Parts.Params[i]); Result << ""; } if (Parts.Exceptions.size() != 0) { Result << ""; for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i) visit(Parts.Exceptions[i]); Result << ""; } if (Parts.Returns.size() != 0) { Result << ""; for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) visit(Parts.Returns[i]); Result << ""; } if (DI->CommentDecl->hasAttrs()) { const AttrVec &Attrs = DI->CommentDecl->getAttrs(); for (unsigned i = 0, e = Attrs.size(); i != e; i++) { const AvailabilityAttr *AA = dyn_cast(Attrs[i]); if (!AA) { if (const DeprecatedAttr *DA = dyn_cast(Attrs[i])) { if (DA->getMessage().empty()) Result << ""; else { Result << ""; appendToResultWithXMLEscaping(DA->getMessage()); Result << ""; } } else if (const UnavailableAttr *UA = dyn_cast(Attrs[i])) { if (UA->getMessage().empty()) Result << ""; else { Result << ""; appendToResultWithXMLEscaping(UA->getMessage()); Result << ""; } } continue; } // 'availability' attribute. Result << "getPlatform()) { Distribution = AvailabilityAttr::getPrettyPlatformName( AA->getPlatform()->getName()); if (Distribution.empty()) Distribution = AA->getPlatform()->getName(); } Result << " distribution=\"" << Distribution << "\">"; VersionTuple IntroducedInVersion = AA->getIntroduced(); if (!IntroducedInVersion.empty()) { Result << "" << IntroducedInVersion.getAsString() << ""; } VersionTuple DeprecatedInVersion = AA->getDeprecated(); if (!DeprecatedInVersion.empty()) { Result << "" << DeprecatedInVersion.getAsString() << ""; } VersionTuple RemovedAfterVersion = AA->getObsoleted(); if (!RemovedAfterVersion.empty()) { Result << "" << RemovedAfterVersion.getAsString() << ""; } StringRef DeprecationSummary = AA->getMessage(); if (!DeprecationSummary.empty()) { Result << ""; appendToResultWithXMLEscaping(DeprecationSummary); Result << ""; } if (AA->getUnavailable()) Result << ""; Result << ""; } } { bool StartTagEmitted = false; for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { const Comment *C = Parts.MiscBlocks[i]; if (FirstParagraphIsBrief && C == Parts.FirstParagraph) continue; if (!StartTagEmitted) { Result << ""; StartTagEmitted = true; } visit(C); } if (StartTagEmitted) Result << ""; } Result << RootEndTag; } void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) { for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { const char C = *I; switch (C) { case '&': Result << "&"; break; case '<': Result << "<"; break; case '>': Result << ">"; break; case '"': Result << """; break; case '\'': Result << "'"; break; default: Result << C; break; } } } void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) { if (S.empty()) return; Result << ""); if (Pos == 0) { Result << "]]]]>"; S = S.drop_front(3); continue; } if (Pos == StringRef::npos) Pos = S.size(); Result << S.substr(0, Pos); S = S.drop_front(Pos); } Result << "]]>"; } CommentToXMLConverter::CommentToXMLConverter() {} CommentToXMLConverter::~CommentToXMLConverter() {} void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC, SmallVectorImpl &HTML, const ASTContext &Context) { CommentASTToHTMLConverter Converter(FC, HTML, Context.getCommentCommandTraits()); Converter.visit(FC); } void CommentToXMLConverter::convertHTMLTagNodeToText( const comments::HTMLTagComment *HTC, SmallVectorImpl &Text, const ASTContext &Context) { CommentASTToHTMLConverter Converter(nullptr, Text, Context.getCommentCommandTraits()); Converter.visit(HTC); } void CommentToXMLConverter::convertCommentToXML(const FullComment *FC, SmallVectorImpl &XML, const ASTContext &Context) { CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(), Context.getSourceManager()); Converter.visit(FC); }