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