Doxygen
Loading...
Searching...
No Matches
docparser.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2022 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15
16#include <stdio.h>
17#include <stdlib.h>
18#include <cassert>
19
20#include <ctype.h>
21
22#include "classlist.h"
23#include "cmdmapper.h"
24#include "config.h"
25#include "debug.h"
26#include "dir.h"
27#include "docparser.h"
28#include "docparser_p.h"
29#include "doxygen.h"
30#include "filedef.h"
31#include "fileinfo.h"
32#include "groupdef.h"
33#include "namespacedef.h"
34#include "message.h"
35#include "pagedef.h"
36#include "portable.h"
37#include "printdocvisitor.h"
38#include "util.h"
39#include "indexlist.h"
40#include "trace.h"
41#include "stringutil.h"
42
43#if !ENABLE_DOCPARSER_TRACING
44#undef AUTO_TRACE
45#undef AUTO_TRACE_ADD
46#undef AUTO_TRACE_EXIT
47#define AUTO_TRACE(...) (void)0
48#define AUTO_TRACE_ADD(...) (void)0
49#define AUTO_TRACE_EXIT(...) (void)0
50#endif
51
52
53//---------------------------------------------------------------------------
54
56{
57 return std::make_unique<DocParser>();
58}
59
61{
62 //QCString indent;
63 //indent.fill(' ',contextStack.size()*2+2);
64 //printf("%sdocParserPushContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
65
66 tokenizer.pushContext();
67 contextStack.emplace();
68 auto &ctx = contextStack.top();
69 ctx = context;
70 ctx.lineNo = tokenizer.getLineNr();
71 context.token = tokenizer.token();
72}
73
75{
76 auto &ctx = contextStack.top();
77 context = ctx;
78 tokenizer.setLineNr(ctx.lineNo);
79 contextStack.pop();
80 tokenizer.popContext();
81 context.token = tokenizer.token();
82
83 //QCString indent;
84 //indent.fill(' ',contextStack.size()*2+2);
85 //printf("%sdocParserPopContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
86}
87
88//---------------------------------------------------------------------------
89
90/*! search for an image in the imageNameDict and if found
91 * copies the image to the output directory (which depends on the \a type
92 * parameter).
93 */
95{
96 QCString result;
97 bool ambig = false;
99 //printf("Search for %s\n",fileName);
100 if (fd)
101 {
102 if (ambig & doWarn)
103 {
104 warn_doc_error(context.fileName,tokenizer.getLineNr(),
105 "image file name '{}' is ambiguous.\n"
106 "Possible candidates:\n{}", fileName,showFileDefMatches(Doxygen::imageNameLinkedMap,fileName));
107 }
108
109 QCString inputFile = fd->absFilePath();
110 FileInfo infi(inputFile.str());
111 if (infi.exists())
112 {
113 result = fileName;
114 int i = result.findRev('/');
115 if (i!=-1 || (i=result.findRev('\\'))!=-1)
116 {
117 result = result.right(static_cast<int>(result.length())-i-1);
118 }
119 //printf("fileName=%s result=%s\n",fileName,qPrint(result));
120 QCString outputDir;
121 switch(type)
122 {
123 case DocImage::Html:
124 if (!Config_getBool(GENERATE_HTML)) return result;
125 outputDir = Config_getString(HTML_OUTPUT);
126 break;
127 case DocImage::Latex:
128 if (!Config_getBool(GENERATE_LATEX)) return result;
129 outputDir = Config_getString(LATEX_OUTPUT);
130 break;
132 if (!Config_getBool(GENERATE_DOCBOOK)) return result;
133 outputDir = Config_getString(DOCBOOK_OUTPUT);
134 break;
135 case DocImage::Rtf:
136 if (!Config_getBool(GENERATE_RTF)) return result;
137 outputDir = Config_getString(RTF_OUTPUT);
138 break;
139 case DocImage::Xml:
140 if (!Config_getBool(GENERATE_XML)) return result;
141 outputDir = Config_getString(XML_OUTPUT);
142 break;
143 }
144 QCString outputFile = outputDir+"/"+result;
145 FileInfo outfi(outputFile.str());
146 if (outfi.isSymLink())
147 {
148 Dir().remove(outputFile.str());
149 warn_doc_error(context.fileName,tokenizer.getLineNr(),
150 "destination of image {} is a symlink, replacing with image",
151 outputFile);
152 }
153 if (outputFile!=inputFile) // prevent copying to ourself
154 {
155 if (copyFile(inputFile,outputFile) && type==DocImage::Html)
156 {
157 Doxygen::indexList->addImageFile(result);
158 }
159 }
160 }
161 else
162 {
163 warn_doc_error(context.fileName,tokenizer.getLineNr(),
164 "could not open image {}",fileName);
165 }
166
167 if (type==DocImage::Latex && Config_getBool(USE_PDFLATEX) &&
168 fd->name().endsWith(".eps")
169 )
170 { // we have an .eps image in pdflatex mode => convert it to a pdf.
171 QCString outputDir = Config_getString(LATEX_OUTPUT);
172 QCString baseName = fd->name().left(fd->name().length()-4);
173 QCString epstopdfArgs(4096, QCString::ExplicitSize);
174 epstopdfArgs.sprintf("\"%s/%s.eps\" --outfile=\"%s/%s.pdf\"",
175 qPrint(outputDir), qPrint(baseName),
176 qPrint(outputDir), qPrint(baseName));
177 if (Portable::system("epstopdf",epstopdfArgs)!=0)
178 {
179 err("Problems running epstopdf. Check your TeX installation!\n");
180 }
181 else
182 {
183 Dir().remove(outputDir.str()+"/"+baseName.str()+".eps");
184 }
185 return baseName;
186 }
187 }
188 else
189 {
190 result=fileName;
191 if (!result.startsWith("http:") && !result.startsWith("https:") && doWarn)
192 {
193 warn_doc_error(context.fileName,tokenizer.getLineNr(),
194 "image file {} is not found in IMAGE_PATH: "
195 "assuming external image.",fileName
196 );
197 }
198 }
199 return result;
200}
201
202/*! Collects the parameters found with \@param command
203 * in a list context.paramsFound. If
204 * the parameter is not an actual parameter of the current
205 * member context.memberDef, then a warning is raised (unless warnings
206 * are disabled altogether).
207 */
209{
210 if (!(Config_getBool(WARN_IF_DOC_ERROR) || Config_getBool(WARN_IF_INCOMPLETE_DOC))) return;
211 if (context.memberDef==nullptr) return; // not a member
212 std::string name = context.token->name.str();
213 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
214 context.memberDef->argumentList() :
215 context.memberDef->declArgumentList();
216 SrcLangExt lang = context.memberDef->getLanguage();
217 //printf("isDocsForDefinition()=%d\n",context.memberDef->isDocsForDefinition());
218 if (al.empty()) return; // no argument list
219
220 static const reg::Ex re(R"(\$?\w+\.*)");
221 reg::Iterator it(name,re);
223 for (; it!=end ; ++it)
224 {
225 const auto &match = *it;
226 QCString aName=match.str();
227 if (lang==SrcLangExt::Fortran) aName=aName.lower();
228 //printf("aName='%s'\n",qPrint(aName));
229 bool found=FALSE;
230 for (const Argument &a : al)
231 {
232 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
233 if (lang==SrcLangExt::Fortran) argName=argName.lower();
234 argName=argName.stripWhiteSpace();
235 //printf("argName='%s' aName=%s\n",qPrint(argName),qPrint(aName));
236 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
237 if (aName==argName)
238 {
239 context.paramsFound.insert(aName.str());
240 found=TRUE;
241 break;
242 }
243 }
244 if (!found)
245 {
246 //printf("member type=%d\n",context.memberDef->memberType());
247 QCString scope=context.memberDef->getScopeString();
248 if (!scope.isEmpty()) scope+="::"; else scope="";
249 QCString inheritedFrom = "";
250 QCString docFile = context.memberDef->docFile();
251 int docLine = context.memberDef->docLine();
252 const MemberDef *inheritedMd = context.memberDef->inheritsDocsFrom();
253 if (inheritedMd) // documentation was inherited
254 {
255 inheritedFrom.sprintf(" inherited from member %s at line "
256 "%d in file %s",qPrint(inheritedMd->name()),
257 inheritedMd->docLine(),qPrint(inheritedMd->docFile()));
258 docFile = context.memberDef->getDefFileName();
259 docLine = context.memberDef->getDefLine();
260 }
261 QCString alStr = argListToString(al);
262 warn_doc_error(docFile,docLine,
263 "argument '{}' of command @param "
264 "is not found in the argument list of {}{}{}{}",
265 aName, scope, context.memberDef->name(),
266 alStr, inheritedFrom);
267 }
268 }
269}
270/*! Collects the return values found with \@retval command
271 * in a global list g_parserContext.retvalsFound.
272 */
274{
275 QCString name = context.token->name;
276 if (!Config_getBool(WARN_IF_DOC_ERROR)) return;
277 if (context.memberDef==nullptr || name.isEmpty()) return; // not a member or no valid name
278 if (context.retvalsFound.count(name.str())==1) // only report the first double entry
279 {
280 warn_doc_error(context.memberDef->getDefFileName(),
281 context.memberDef->getDefLine(),
282 "return value '{}' of {} has multiple documentation sections",
283 name, context.memberDef->qualifiedName());
284 }
285 context.retvalsFound.insert(name.str());
286}
287
288/*! Checks if the parameters that have been specified using \@param are
289 * indeed all parameters and that a parameter does not have multiple
290 * \@param blocks.
291 * Must be called after checkArgumentName() has been called for each
292 * argument.
293 */
295{
296 if (context.memberDef && context.hasParamCommand)
297 {
298 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
299 context.memberDef->argumentList() :
300 context.memberDef->declArgumentList();
301 SrcLangExt lang = context.memberDef->getLanguage();
302 if (!al.empty())
303 {
304 ArgumentList undocParams;
305 for (const Argument &a: al)
306 {
307 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
308 if (lang==SrcLangExt::Fortran) argName = argName.lower();
309 argName=argName.stripWhiteSpace();
310 QCString aName = argName;
311 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
312 if (lang==SrcLangExt::Python && (argName=="self" || argName=="cls"))
313 {
314 // allow undocumented self / cls parameter for Python
315 }
316 else if (lang==SrcLangExt::Cpp && (a.type=="this" || a.type.startsWith("this ")))
317 {
318 // allow undocumented this (for C++23 deducing this), see issue #11123
319 }
320 else if (!argName.isEmpty())
321 {
322 size_t count = context.paramsFound.count(argName.str());
323 if (count==0 && a.docs.isEmpty())
324 {
325 undocParams.push_back(a);
326 }
327 else if (count>1 && Config_getBool(WARN_IF_DOC_ERROR))
328 {
329 warn_doc_error(context.memberDef->docFile(),
330 context.memberDef->docLine(),
331 "argument {} from the argument list of {} has multiple @param documentation sections",
332 aName, context.memberDef->qualifiedName());
333 }
334 }
335 }
336 if (!undocParams.empty() && Config_getBool(WARN_IF_INCOMPLETE_DOC))
337 {
338 bool first=TRUE;
339 QCString errMsg = "The following parameter";
340 if (undocParams.size()>1) errMsg+="s";
341 errMsg+=" of "+
342 QCString(context.memberDef->qualifiedName()) +
344 (undocParams.size()>1 ? " are" : " is") + " not documented:\n";
345 for (const Argument &a : undocParams)
346 {
347 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
348 if (lang==SrcLangExt::Fortran) argName = argName.lower();
349 argName=argName.stripWhiteSpace();
350 if (!first) errMsg+="\n";
351 first=FALSE;
352 errMsg+=" parameter '"+argName+"'";
353 }
354 warn_incomplete_doc(context.memberDef->docFile(), context.memberDef->docLine(), "{}", errMsg);
355 }
356 }
357 else
358 {
359 if (context.paramsFound.empty() && Config_getBool(WARN_IF_DOC_ERROR))
360 {
361 warn_doc_error(context.memberDef->docFile(),
362 context.memberDef->docLine(),
363 "{} has @param documentation sections but no arguments",
364 context.memberDef->qualifiedName());
365 }
366 }
367 }
368}
369
370
371//---------------------------------------------------------------------------
372
373//---------------------------------------------------------------------------
374
375//---------------------------------------------------------------------------
376/*! Looks for a documentation block with name commandName in the current
377 * context (g_parserContext.context). The resulting documentation string is
378 * put in pDoc, the definition in which the documentation was found is
379 * put in pDef.
380 * @retval TRUE if name was found.
381 * @retval FALSE if name was not found.
382 */
384 QCString *pDoc,
385 QCString *pBrief,
386 const Definition **pDef)
387{
388 AUTO_TRACE("commandName={}",commandName);
389 *pDoc="";
390 *pBrief="";
391 *pDef=nullptr;
392 QCString cmdArg=commandName;
393 if (cmdArg.isEmpty())
394 {
395 AUTO_TRACE_EXIT("empty");
396 return false;
397 }
398
399 const FileDef *fd=nullptr;
400 const GroupDef *gd=nullptr;
401 const PageDef *pd=nullptr;
402 gd = Doxygen::groupLinkedMap->find(cmdArg);
403 if (gd) // group
404 {
405 *pDoc=gd->documentation();
406 *pBrief=gd->briefDescription();
407 *pDef=gd;
408 AUTO_TRACE_EXIT("group");
409 return true;
410 }
411 pd = Doxygen::pageLinkedMap->find(cmdArg);
412 if (pd) // page
413 {
414 *pDoc=pd->documentation();
415 *pBrief=pd->briefDescription();
416 *pDef=pd;
417 AUTO_TRACE_EXIT("page");
418 return true;
419 }
420 bool ambig = false;
421 fd = findFileDef(Doxygen::inputNameLinkedMap,cmdArg,ambig);
422 if (fd && !ambig) // file
423 {
424 *pDoc=fd->documentation();
425 *pBrief=fd->briefDescription();
426 *pDef=fd;
427 AUTO_TRACE_EXIT("file");
428 return true;
429 }
430
431 // for symbols we need to normalize the separator, so A#B, or A\B, or A.B becomes A::B
432 cmdArg = substitute(cmdArg,"#","::");
433 cmdArg = substitute(cmdArg,"\\","::");
434 bool extractAnonNs = Config_getBool(EXTRACT_ANON_NSPACES);
435 if (extractAnonNs &&
436 cmdArg.startsWith("anonymous_namespace{")
437 )
438 {
439 size_t rightBracePos = cmdArg.find("}", static_cast<int>(qstrlen("anonymous_namespace{")));
440 QCString leftPart = cmdArg.left(rightBracePos + 1);
441 QCString rightPart = cmdArg.right(cmdArg.size() - rightBracePos - 1);
442 rightPart = substitute(rightPart, ".", "::");
443 cmdArg = leftPart + rightPart;
444 }
445 else
446 {
447 cmdArg = substitute(cmdArg,".","::");
448 }
449
450 int l=static_cast<int>(cmdArg.length());
451
452 int funcStart=cmdArg.find('(');
453 if (funcStart==-1)
454 {
455 funcStart=l;
456 }
457 else
458 {
459 // Check for the case of operator() and the like.
460 // beware of scenarios like operator()((foo)bar)
461 int secondParen = cmdArg.find('(', funcStart+1);
462 int leftParen = cmdArg.find(')', funcStart+1);
463 if (leftParen!=-1 && secondParen!=-1)
464 {
465 if (leftParen<secondParen)
466 {
467 funcStart=secondParen;
468 }
469 }
470 }
471
472 QCString name=removeRedundantWhiteSpace(cmdArg.left(funcStart));
473 QCString args=cmdArg.right(l-funcStart);
474 // try if the link is to a member
475 GetDefInput input(
476 context.context.find('.')==-1 ? context.context : QCString(), // find('.') is a hack to detect files
477 name,
478 args);
479 input.checkCV=true;
480 GetDefResult result = getDefs(input);
481 //printf("found=%d context=%s name=%s\n",result.found,qPrint(context.context),qPrint(name));
482 if (result.found && result.md)
483 {
484 *pDoc=result.md->documentation();
485 *pBrief=result.md->briefDescription();
486 *pDef=result.md;
487 AUTO_TRACE_EXIT("member");
488 return true;
489 }
490
491 int scopeOffset=static_cast<int>(context.context.length());
492 do // for each scope
493 {
494 QCString fullName=cmdArg;
495 if (scopeOffset>0)
496 {
497 fullName.prepend(context.context.left(scopeOffset)+"::");
498 }
499 //printf("Trying fullName='%s'\n",qPrint(fullName));
500
501 // try class, namespace, group, page, file reference
502 const ClassDef *cd = Doxygen::classLinkedMap->find(fullName);
503 if (cd) // class
504 {
505 *pDoc=cd->documentation();
506 *pBrief=cd->briefDescription();
507 *pDef=cd;
508 AUTO_TRACE_EXIT("class");
509 return true;
510 }
511 const NamespaceDef *nd = Doxygen::namespaceLinkedMap->find(fullName);
512 if (nd) // namespace
513 {
514 *pDoc=nd->documentation();
515 *pBrief=nd->briefDescription();
516 *pDef=nd;
517 AUTO_TRACE_EXIT("namespace");
518 return true;
519 }
520 if (scopeOffset==0)
521 {
522 scopeOffset=-1;
523 }
524 else
525 {
526 scopeOffset = context.context.findRev("::",scopeOffset-1);
527 if (scopeOffset==-1) scopeOffset=0;
528 }
529 } while (scopeOffset>=0);
530
531 AUTO_TRACE_EXIT("not found");
532 return FALSE;
533}
534
535//---------------------------------------------------------------------------
537 DocNodeList &children,const QCString &txt)
538{
539 switch (tok.value())
540 {
541 case TokenRetval::TK_COMMAND_AT:
542 // fall through
543 case TokenRetval::TK_COMMAND_BS:
544 {
545 char cs[2] = { tok.command_to_char(), 0 };
546 children.append<DocWord>(this,parent,cs + context.token->name);
547 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Illegal command '{:c}{}' found as part of a {}",
548 tok.command_to_char(),context.token->name,txt);
549 }
550 break;
551 case TokenRetval::TK_SYMBOL:
552 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported symbol '{}' found as part of a {}",
553 qPrint(context.token->name), qPrint(txt));
554 break;
555 case TokenRetval::TK_HTMLTAG:
556 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported HTML tag <{}{}> found as part of a {}",
557 context.token->endTag ? "/" : "",context.token->name, txt);
558 break;
559 default:
560 children.append<DocWord>(this,parent,context.token->name);
561 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unexpected token {} found as part of a {}",
562 tok.to_string(), txt);
563 break;
564 }
565}
566
567//---------------------------------------------------------------------------
568
570{
571 AUTO_TRACE("cmdName={}",cmdName);
572 QCString saveCmdName = cmdName;
573 Token tok=tokenizer.lex();
574 if (!tok.is(TokenRetval::TK_WHITESPACE))
575 {
576 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\{} command",
577 saveCmdName);
578 return tok;
579 }
580 tok = tokenizer.lex();
581 while (!tok.is_any_of(TokenRetval::TK_NONE, TokenRetval::TK_EOF, TokenRetval::TK_WHITESPACE,
582 TokenRetval::TK_NEWPARA, TokenRetval::TK_LISTITEM, TokenRetval::TK_ENDLIST)
583 )
584 {
585 static const reg::Ex specialChar(R"([.,|()\[\]:;?])");
586 if (tok.is(TokenRetval::TK_WORD) && context.token->name.length()==1 &&
587 reg::match(context.token->name.str(),specialChar))
588 {
589 // special character that ends the markup command
590 return tok;
591 }
592 if (!defaultHandleToken(parent,tok,children))
593 {
594 switch (tok.value())
595 {
596 case TokenRetval::TK_HTMLTAG:
597 if (insideLI(parent) && Mappers::htmlTagMapper->map(context.token->name)!=HtmlTagType::UNKNOWN && context.token->endTag)
598 {
599 // ignore </li> as the end of a style command
600 }
601 else
602 {
603 AUTO_TRACE_EXIT("end tok={}",tok.to_string());
604 return tok;
605 }
606 break;
607 default:
608 errorHandleDefaultToken(parent,tok,children,"\\" + saveCmdName + " command");
609 break;
610 }
611 break;
612 }
613 tok = tokenizer.lex();
614 }
615 AUTO_TRACE_EXIT("end tok={}",tok.to_string());
616 return (tok.is_any_of(TokenRetval::TK_NEWPARA,TokenRetval::TK_LISTITEM,TokenRetval::TK_ENDLIST)) ? tok : Token::make_RetVal_OK();
617}
618
619/*! Called when a style change starts. For instance a <b> command is
620 * encountered.
621 */
623 DocStyleChange::Style s,const QCString &tagName,const HtmlAttribList *attribs)
624{
625 AUTO_TRACE("tagName={}",tagName);
626 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),s,tagName,TRUE,
627 context.fileName,tokenizer.getLineNr(),attribs);
628 context.styleStack.push(&children.back());
629}
630
631/*! Called when a style change ends. For instance a </b> command is
632 * encountered.
633 */
635 DocStyleChange::Style s,const QCString &tagName)
636{
637 AUTO_TRACE("tagName={}",tagName);
638 QCString tagNameLower = QCString(tagName).lower();
639
640 auto topStyleChange = [](const DocStyleChangeStack &stack) -> const DocStyleChange &
641 {
642 return std::get<DocStyleChange>(*stack.top());
643 };
644
645 if (context.styleStack.empty() || // no style change
646 topStyleChange(context.styleStack).style()!=s || // wrong style change
647 topStyleChange(context.styleStack).tagName()!=tagNameLower || // wrong style change
648 topStyleChange(context.styleStack).position()!=context.nodeStack.size() // wrong position
649 )
650 {
651 if (context.styleStack.empty())
652 {
653 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </{0}> tag without matching <{0}>",tagName);
654 }
655 else if (topStyleChange(context.styleStack).tagName()!=tagNameLower ||
656 topStyleChange(context.styleStack).style()!=s)
657 {
658 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </{}> tag while expecting </{}>",
659 tagName,topStyleChange(context.styleStack).tagName());
660 }
661 else
662 {
663 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </{}> at different nesting level ({}) than expected ({})",
664 tagName,context.nodeStack.size(),topStyleChange(context.styleStack).position());
665 }
666 }
667 else // end the section
668 {
669 children.append<DocStyleChange>(
670 this,parent,context.nodeStack.size(),s,
671 topStyleChange(context.styleStack).tagName(),FALSE);
672 context.styleStack.pop();
673 }
674}
675
676/*! Called at the end of a paragraph to close all open style changes
677 * (e.g. a <b> without a </b>). The closed styles are pushed onto a stack
678 * and entered again at the start of a new paragraph.
679 */
681{
682 AUTO_TRACE();
683 if (!context.styleStack.empty())
684 {
685 const DocStyleChange *sc = &std::get<DocStyleChange>(*context.styleStack.top());
686 while (sc && sc->position()>=context.nodeStack.size())
687 { // there are unclosed style modifiers in the paragraph
688 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),
689 sc->style(),sc->tagName(),FALSE);
690 context.initialStyleStack.push(context.styleStack.top());
691 context.styleStack.pop();
692 sc = !context.styleStack.empty() ? &std::get<DocStyleChange>(*context.styleStack.top()) : nullptr;
693 }
694 }
695}
696
698{
699 AUTO_TRACE();
700 while (!context.initialStyleStack.empty())
701 {
702 const DocStyleChange &sc = std::get<DocStyleChange>(*context.initialStyleStack.top());
703 handleStyleEnter(parent,children,sc.style(),sc.tagName(),&sc.attribs());
704 context.initialStyleStack.pop();
705 }
706}
707
709 const HtmlAttribList &tagHtmlAttribs)
710{
711 AUTO_TRACE();
712 size_t index=0;
713 Token retval = Token::make_RetVal_OK();
714 for (const auto &opt : tagHtmlAttribs)
715 {
716 if (opt.name=="name" || opt.name=="id") // <a name=label> or <a id=label> tag
717 {
718 if (!opt.value.isEmpty())
719 {
720 children.append<DocAnchor>(this,parent,opt.value,TRUE);
721 break; // stop looking for other tag attribs
722 }
723 else
724 {
725 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <a> tag with name option but without value!");
726 }
727 }
728 else if (opt.name=="href") // <a href=url>..</a> tag
729 {
730 // copy attributes
731 HtmlAttribList attrList = tagHtmlAttribs;
732 // and remove the href attribute
733 attrList.erase(attrList.begin()+index);
734 QCString relPath;
735 if (opt.value.at(0) != '#') relPath = context.relPath;
736 children.append<DocHRef>(this, parent, attrList,
737 opt.value, relPath,
738 convertNameToFile(context.fileName, FALSE, TRUE));
739 context.insideHtmlLink=TRUE;
740 retval = children.get_last<DocHRef>()->parse();
741 context.insideHtmlLink=FALSE;
742 tokenizer.setStatePara();
743 break;
744 }
745 else // unsupported option for tag a
746 {
747 }
748 ++index;
749 }
750 return retval;
751}
752
754{
755 AUTO_TRACE();
756 if (!context.initialStyleStack.empty())
757 {
758 QCString tagName = std::get<DocStyleChange>(*context.initialStyleStack.top()).tagName();
759 QCString fileName = std::get<DocStyleChange>(*context.initialStyleStack.top()).fileName();
760 int lineNr = std::get<DocStyleChange>(*context.initialStyleStack.top()).lineNr();
761 context.initialStyleStack.pop();
763 if (lineNr != -1)
764 {
765 warn_doc_error(context.fileName,tokenizer.getLineNr(),
766 "end of comment block while expecting "
767 "command </{}> (Probable start '{}' at line {})",tagName, fileName, lineNr);
768 }
769 else
770 {
771 warn_doc_error(context.fileName,tokenizer.getLineNr(),
772 "end of comment block while expecting command </{}>",tagName);
773 }
774 }
775}
776
777void DocParser::handleLinkedWord(DocNodeVariant *parent,DocNodeList &children,bool ignoreAutoLinkFlag)
778{
779 // helper to check if word w starts with any of the words in AUTOLINK_IGNORE_WORDS
780 auto ignoreWord = [](const QCString &w) -> bool {
781 const auto &list = Config_getList(AUTOLINK_IGNORE_WORDS);
782 return std::find_if(list.begin(), list.end(),
783 [&w](const auto &ignore) { return w.startsWith(ignore); }
784 )!=list.end();
785 };
786 QCString name = linkToText(context.lang,context.token->name,TRUE);
787 AUTO_TRACE("word={}",name);
788 bool autolinkSupport = Config_getBool(AUTOLINK_SUPPORT);
789 if ((!autolinkSupport && !ignoreAutoLinkFlag) || ignoreWord(context.token->name)) // no autolinking -> add as normal word
790 {
791 children.append<DocWord>(this,parent,name);
792 return;
793 }
794
795 // ------- try to turn the word 'name' into a link
796
797 const Definition *compound=nullptr;
798 const MemberDef *member=nullptr;
799 size_t len = context.token->name.length();
800 ClassDef *cd=nullptr;
801 bool ambig = false;
803 auto lang = context.lang;
804 //printf("handleLinkedWord(%s) context.context=%s\n",qPrint(context.token->name),qPrint(context.context));
805 if (!context.insideHtmlLink &&
806 (resolveRef(context.context,context.token->name,context.inSeeBlock,&compound,&member,lang,TRUE,fd,TRUE)
807 || (!context.context.isEmpty() && // also try with global scope
808 resolveRef(QCString(),context.token->name,context.inSeeBlock,&compound,&member,lang,FALSE,nullptr,TRUE))
809 )
810 )
811 {
812 //printf("ADD %s = %p (linkable?=%d)\n",qPrint(context.token->name),(void*)member,member ? member->isLinkable() : FALSE);
813 if (member && member->isLinkable()) // member link
814 {
815 AUTO_TRACE_ADD("resolved reference as member link");
816 if (member->isObjCMethod())
817 {
818 bool localLink = context.memberDef ? member->getClassDef()==context.memberDef->getClassDef() : FALSE;
819 name = member->objCMethodName(localLink,context.inSeeBlock);
820 }
821 children.append<DocLinkedWord>(
822 this,parent,name,
823 member->getReference(),
824 member->getOutputFileBase(),
825 member->anchor(),
826 member->briefDescriptionAsTooltip());
827 }
828 else if (compound->isLinkable()) // compound link
829 {
830 AUTO_TRACE_ADD("resolved reference as compound link");
831 QCString anchor = compound->anchor();
832 if (compound->definitionType()==Definition::TypeFile)
833 {
834 name=context.token->name;
835 }
836 else if (compound->definitionType()==Definition::TypeGroup)
837 {
838 name=toGroupDef(compound)->groupTitle();
839 }
840 children.append<DocLinkedWord>(
841 this,parent,name,
842 compound->getReference(),
843 compound->getOutputFileBase(),
844 anchor,
845 compound->briefDescriptionAsTooltip());
846 }
847 else if (compound->definitionType()==Definition::TypeFile &&
848 (toFileDef(compound))->generateSourceFile()
849 ) // undocumented file that has source code we can link to
850 {
851 AUTO_TRACE_ADD("resolved reference as source link");
852 children.append<DocLinkedWord>(
853 this,parent,context.token->name,
854 compound->getReference(),
855 compound->getSourceFileBase(),
856 "",
857 compound->briefDescriptionAsTooltip());
858 }
859 else // not linkable
860 {
861 AUTO_TRACE_ADD("resolved reference as unlinkable compound={} (linkable={}) member={} (linkable={})",
862 compound ? compound->name() : "<none>", compound ? (int)compound->isLinkable() : -1,
863 member ? member->name() : "<none>", member ? (int)member->isLinkable() : -1);
864 children.append<DocWord>(this,parent,name);
865 }
866 }
867 else if (!context.insideHtmlLink && len>1 && context.token->name.at(len-1)==':')
868 {
869 // special case, where matching Foo: fails to be an Obj-C reference,
870 // but Foo itself might be linkable.
871 context.token->name=context.token->name.left(len-1);
872 handleLinkedWord(parent,children,ignoreAutoLinkFlag);
873 children.append<DocWord>(this,parent,":");
874 }
875 else if (!context.insideHtmlLink && (cd=getClass(context.token->name+"-p")))
876 {
877 // special case 2, where the token name is not a class, but could
878 // be a Obj-C protocol
879 children.append<DocLinkedWord>(
880 this,parent,name,
881 cd->getReference(),
882 cd->getOutputFileBase(),
883 cd->anchor(),
885 }
886 else // normal non-linkable word
887 {
888 AUTO_TRACE_ADD("non-linkable");
889 if (context.token->name.startsWith("#"))
890 {
891 warn_doc_error(context.fileName,tokenizer.getLineNr(),"explicit link request to '{}' could not be resolved",name);
892 }
893 children.append<DocWord>(this,parent,context.token->name);
894 }
895}
896
898{
899 QCString name = context.token->name; // save token name
900 AUTO_TRACE("name={}",name);
901 QCString name1;
902 int p=0, i=0, ii=0;
903 while ((i=paramTypes.find('|',p))!=-1)
904 {
905 name1 = paramTypes.mid(p,i-p);
906 ii=name1.find('[');
907 context.token->name=ii!=-1 ? name1.mid(0,ii) : name1; // take part without []
908 handleLinkedWord(parent,children);
909 if (ii!=-1) children.append<DocWord>(this,parent,name1.mid(ii)); // add [] part
910 p=i+1;
911 children.append<DocSeparator>(this,parent,"|");
912 }
913 name1 = paramTypes.mid(p);
914 ii=name1.find('[');
915 context.token->name=ii!=-1 ? name1.mid(0,ii) : name1;
916 handleLinkedWord(parent,children);
917 if (ii!=-1) children.append<DocWord>(this,parent,name1.mid(ii));
918 context.token->name = name; // restore original token name
919}
920
922{
923 Token tok=tokenizer.lex();
924 QCString tokenName = context.token->name;
925 AUTO_TRACE("name={}",tokenName);
926 if (!tok.is(TokenRetval::TK_WHITESPACE))
927 {
928 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\{} command", tokenName);
929 return;
930 }
931 tokenizer.setStateInternalRef();
932 tok=tokenizer.lex(); // get the reference id
933 if (!tok.is_any_of(TokenRetval::TK_WORD,TokenRetval::TK_LNKWORD))
934 {
935 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token {} as the argument of {}",
936 tok.to_string(),tokenName);
937 return;
938 }
939 children.append<DocInternalRef>(this,parent,context.token->name);
940 children.get_last<DocInternalRef>()->parse();
941}
942
944{
945 AUTO_TRACE();
946 Token tok=tokenizer.lex();
947 if (!tok.is(TokenRetval::TK_WHITESPACE))
948 {
949 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\{} command",
950 context.token->name);
951 return;
952 }
953 tokenizer.setStateAnchor();
954 tok=tokenizer.lex();
955 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
956 {
957 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected end of comment block while parsing the "
958 "argument of command {}",context.token->name);
959 return;
960 }
961 else if (!tok.is_any_of(TokenRetval::TK_WORD,TokenRetval::TK_LNKWORD))
962 {
963 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token {} as the argument of {}",
964 tok.to_string(),context.token->name);
965 return;
966 }
967 tokenizer.setStatePara();
968 children.append<DocAnchor>(this,parent,context.token->name,FALSE);
969}
970
972{
973 AUTO_TRACE();
974 Token tok=tokenizer.lex();
975 if (!tok.is(TokenRetval::TK_WHITESPACE))
976 {
977 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\{} command", context.token->name);
978 return;
979 }
980 tokenizer.setStatePrefix();
981 tok=tokenizer.lex();
982 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
983 {
984 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected end of comment block while parsing the "
985 "argument of command {}",context.token->name);
986 return;
987 }
988 else if (!tok.is(TokenRetval::TK_WORD))
989 {
990 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token {} as the argument of {}",
991 tok.to_string(),context.token->name);
992 return;
993 }
994 context.prefix = context.token->name;
995 tokenizer.setStatePara();
996}
997
998/* Helper function that deals with the title, width, and height arguments of various commands.
999 * @param[in] cmd Command id for which to extract caption and size info.
1000 * @param[in] parent Parent node, owner of the children list passed as
1001 * the third argument.
1002 * @param[in] children The list of child nodes to which the node representing
1003 * the token can be added.
1004 * @param[out] width the extracted width specifier
1005 * @param[out] height the extracted height specifier
1006 */
1008{
1009 AUTO_TRACE();
1010 auto ns = AutoNodeStack(this,parent);
1011
1012 // parse title
1013 tokenizer.setStateTitle();
1014 Token tok = tokenizer.lex();
1015 while (!tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1016 {
1017 if (tok.is(TokenRetval::TK_WORD) && (context.token->name=="width=" || context.token->name=="height="))
1018 {
1019 // special case: no title, but we do have a size indicator
1020 break;
1021 }
1022 else if (tok.is(TokenRetval::TK_HTMLTAG))
1023 {
1024 tokenizer.unputString(context.token->text);
1025 break;
1026 }
1027 if (!defaultHandleToken(parent,tok,children))
1028 {
1029 errorHandleDefaultToken(parent,tok,children,Mappers::cmdMapper->find(cmd));
1030 }
1031 tok = tokenizer.lex();
1032 }
1033 // parse size attributes
1034 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1035 {
1036 tok=tokenizer.lex();
1037 }
1038 while (tok.is_any_of(TokenRetval::TK_WHITESPACE,TokenRetval::TK_WORD,TokenRetval::TK_HTMLTAG)) // there are values following the title
1039 {
1040 if (tok.is(TokenRetval::TK_WORD))
1041 {
1042 if (context.token->name=="width=" || context.token->name=="height=")
1043 {
1044 tokenizer.setStateTitleAttrValue();
1045 context.token->name = context.token->name.left(context.token->name.length()-1);
1046 }
1047
1048 if (context.token->name=="width")
1049 {
1050 width = context.token->chars;
1051 }
1052 else if (context.token->name=="height")
1053 {
1054 height = context.token->chars;
1055 }
1056 else // other text after the title -> treat as normal text
1057 {
1058 tokenizer.unputString(context.token->name);
1059 //warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unknown option '{}' after \\{} command, expected 'width' or 'height'",
1060 // context.token->name, Mappers::cmdMapper->find(cmd));
1061 break;
1062 }
1063 }
1064
1065 tok=tokenizer.lex();
1066 // if we found something we did not expect, push it back to the stream
1067 // so it can still be processed
1068 if (tok.is_any_of(TokenRetval::TK_COMMAND_AT,TokenRetval::TK_COMMAND_BS))
1069 {
1070 tokenizer.unputString(context.token->name);
1071 tokenizer.unputString(tok.is(TokenRetval::TK_COMMAND_AT) ? "@" : "\\");
1072 break;
1073 }
1074 else if (tok.is(TokenRetval::TK_SYMBOL))
1075 {
1076 tokenizer.unputString(context.token->name);
1077 break;
1078 }
1079 else if (tok.is(TokenRetval::TK_HTMLTAG))
1080 {
1081 tokenizer.unputString(context.token->text);
1082 break;
1083 }
1084 }
1085 tokenizer.setStatePara();
1086
1088 AUTO_TRACE_EXIT("width={} height={}",width,height);
1089}
1090
1092{
1093 AUTO_TRACE();
1094 bool inlineImage = false;
1095 QCString anchorStr;
1096
1097 Token tok=tokenizer.lex();
1098 if (!tok.is(TokenRetval::TK_WHITESPACE))
1099 {
1100 if (tok.is(TokenRetval::TK_WORD))
1101 {
1102 if (context.token->name == "{")
1103 {
1104 tokenizer.setStateOptions();
1105 tokenizer.lex();
1106 tokenizer.setStatePara();
1107 StringVector optList=split(context.token->name.str(),",");
1108 for (const auto &opt : optList)
1109 {
1110 if (opt.empty()) continue;
1111 QCString locOpt(opt);
1112 QCString locOptLow;
1113 locOpt = locOpt.stripWhiteSpace();
1114 locOptLow = locOpt.lower();
1115 if (locOptLow == "inline")
1116 {
1117 inlineImage = true;
1118 }
1119 else if (locOptLow.startsWith("anchor:"))
1120 {
1121 if (!anchorStr.isEmpty())
1122 {
1123 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1124 "multiple use of option 'anchor' for 'image' command, ignoring: '{}'",
1125 locOpt.mid(7));
1126 }
1127 else
1128 {
1129 anchorStr = locOpt.mid(7);
1130 }
1131 }
1132 else
1133 {
1134 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1135 "unknown option '{}' for 'image' command specified",
1136 locOpt);
1137 }
1138 }
1139 tok=tokenizer.lex();
1140 if (!tok.is(TokenRetval::TK_WHITESPACE))
1141 {
1142 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1143 return;
1144 }
1145 }
1146 }
1147 else
1148 {
1149 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1150 return;
1151 }
1152 }
1153 tok=tokenizer.lex();
1154 if (!tok.is_any_of(TokenRetval::TK_WORD,TokenRetval::TK_LNKWORD))
1155 {
1156 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token {} as the argument of \\image",
1157 tok.to_string());
1158 return;
1159 }
1160 tok=tokenizer.lex();
1161 if (!tok.is(TokenRetval::TK_WHITESPACE))
1162 {
1163 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1164 return;
1165 }
1167 QCString imgType = context.token->name.lower();
1168 if (imgType=="html") t=DocImage::Html;
1169 else if (imgType=="latex") t=DocImage::Latex;
1170 else if (imgType=="docbook") t=DocImage::DocBook;
1171 else if (imgType=="rtf") t=DocImage::Rtf;
1172 else if (imgType=="xml") t=DocImage::Xml;
1173 else
1174 {
1175 warn_doc_error(context.fileName,tokenizer.getLineNr(),"output format `{}` specified as the first argument of "
1176 "\\image command is not valid", imgType);
1177 return;
1178 }
1179 tokenizer.setStateFile();
1180 tok=tokenizer.lex();
1181 tokenizer.setStatePara();
1182 if (!tok.is(TokenRetval::TK_WORD))
1183 {
1184 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token {} as the argument of \\image", tok.to_string());
1185 return;
1186 }
1187 if (!anchorStr.isEmpty())
1188 {
1189 children.append<DocAnchor>(this,parent,anchorStr,true);
1190 }
1191 HtmlAttribList attrList;
1192 children.append<DocImage>(this,parent,attrList,
1193 findAndCopyImage(context.token->name,t),t,"",inlineImage);
1194 children.get_last<DocImage>()->parse();
1195}
1196
1197
1198/* Helper function that deals with the most common tokens allowed in
1199 * title like sections.
1200 * @param parent Parent node, owner of the children list passed as
1201 * the third argument.
1202 * @param tok The token to process.
1203 * @param children The list of child nodes to which the node representing
1204 * the token can be added.
1205 * @param handleWord Indicates if word token should be processed
1206 * @retval TRUE The token was handled.
1207 * @retval FALSE The token was not handled.
1208 */
1210{
1211 AUTO_TRACE("token={} handleWord={}",tok.to_string(),handleWord);
1212 if (tok.is_any_of(TokenRetval::TK_WORD,TokenRetval::TK_LNKWORD,TokenRetval::TK_SYMBOL,TokenRetval::TK_URL,
1213 TokenRetval::TK_COMMAND_AT,TokenRetval::TK_COMMAND_BS,TokenRetval::TK_HTMLTAG)
1214 )
1215 {
1216 }
1217reparsetoken:
1218 QCString tokenName = context.token->name;
1219 AUTO_TRACE_ADD("tokenName={}",tokenName);
1220 switch (tok.value())
1221 {
1222 case TokenRetval::TK_COMMAND_AT:
1223 // fall through
1224 case TokenRetval::TK_COMMAND_BS:
1225 switch (Mappers::cmdMapper->map(tokenName))
1226 {
1229 break;
1232 break;
1235 break;
1238 break;
1241 break;
1244 break;
1247 break;
1250 break;
1253 break;
1257 break;
1262 break;
1265 break;
1268 break;
1271 break;
1274 break;
1277 break;
1280 break;
1283 break;
1285 {
1286 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Italic,tokenName,TRUE);
1287 tok=handleStyleArgument(parent,children,tokenName);
1288 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Italic,tokenName,FALSE);
1289 if (!tok.is(TokenRetval::TK_WORD)) children.append<DocWhiteSpace>(this,parent," ");
1290 if (tok.is(TokenRetval::TK_NEWPARA)) goto handlepara;
1291 else if (tok.is_any_of(TokenRetval::TK_WORD,TokenRetval::TK_HTMLTAG))
1292 {
1293 AUTO_TRACE_ADD("CommandType::CMD_EMPHASIS: reparsing");
1294 goto reparsetoken;
1295 }
1296 }
1297 break;
1299 {
1300 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Bold,tokenName,TRUE);
1301 tok=handleStyleArgument(parent,children,tokenName);
1302 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Bold,tokenName,FALSE);
1303 if (!tok.is(TokenRetval::TK_WORD)) children.append<DocWhiteSpace>(this,parent," ");
1304 if (tok.is(TokenRetval::TK_NEWPARA)) goto handlepara;
1305 else if (tok.is_any_of(TokenRetval::TK_WORD,TokenRetval::TK_HTMLTAG))
1306 {
1307 AUTO_TRACE_ADD("CommandType::CMD_BOLD: reparsing");
1308 goto reparsetoken;
1309 }
1310 }
1311 break;
1313 {
1314 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Code,tokenName,TRUE);
1315 tok=handleStyleArgument(parent,children,tokenName);
1316 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Code,tokenName,FALSE);
1317 if (!tok.is(TokenRetval::TK_WORD)) children.append<DocWhiteSpace>(this,parent," ");
1318 if (tok.is(TokenRetval::TK_NEWPARA)) goto handlepara;
1319 else if (tok.is_any_of(TokenRetval::TK_WORD,TokenRetval::TK_HTMLTAG))
1320 {
1321 AUTO_TRACE_ADD("CommandType::CMD_CODE: reparsing");
1322 goto reparsetoken;
1323 }
1324 }
1325 break;
1327 {
1328 tokenizer.setStateHtmlOnly();
1329 tok = tokenizer.lex();
1330 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::HtmlOnly,context.isExample,context.exampleName,context.token->name=="block");
1331 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1332 {
1333 warn_doc_error(context.fileName,tokenizer.getLineNr(),"htmlonly section ended without end marker");
1334 }
1335 tokenizer.setStatePara();
1336 }
1337 break;
1339 {
1340 tokenizer.setStateManOnly();
1341 tok = tokenizer.lex();
1342 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::ManOnly,context.isExample,context.exampleName);
1343 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1344 {
1345 warn_doc_error(context.fileName,tokenizer.getLineNr(),"manonly section ended without end marker");
1346 }
1347 tokenizer.setStatePara();
1348 }
1349 break;
1351 {
1352 tokenizer.setStateRtfOnly();
1353 tok = tokenizer.lex();
1354 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::RtfOnly,context.isExample,context.exampleName);
1355 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1356 {
1357 warn_doc_error(context.fileName,tokenizer.getLineNr(),"rtfonly section ended without end marker");
1358 }
1359 tokenizer.setStatePara();
1360 }
1361 break;
1363 {
1364 tokenizer.setStateLatexOnly();
1365 tok = tokenizer.lex();
1366 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::LatexOnly,context.isExample,context.exampleName);
1367 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1368 {
1369 warn_doc_error(context.fileName,tokenizer.getLineNr(),"latexonly section ended without end marker");
1370 }
1371 tokenizer.setStatePara();
1372 }
1373 break;
1375 {
1376 tokenizer.setStateXmlOnly();
1377 tok = tokenizer.lex();
1378 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::XmlOnly,context.isExample,context.exampleName);
1379 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1380 {
1381 warn_doc_error(context.fileName,tokenizer.getLineNr(),"xmlonly section ended without end marker");
1382 }
1383 tokenizer.setStatePara();
1384 }
1385 break;
1387 {
1388 tokenizer.setStateDbOnly();
1389 tok = tokenizer.lex();
1390 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::DocbookOnly,context.isExample,context.exampleName);
1391 if (tok.is_any_of(TokenRetval::TK_NONE,TokenRetval::TK_EOF))
1392 {
1393 warn_doc_error(context.fileName,tokenizer.getLineNr(),"docbookonly section ended without end marker");
1394 }
1395 tokenizer.setStatePara();
1396 }
1397 break;
1399 {
1400 children.append<DocFormula>(this,parent,context.token->id);
1401 }
1402 break;
1405 {
1406 handleAnchor(parent,children);
1407 }
1408 break;
1410 {
1411 handlePrefix(parent,children);
1412 }
1413 break;
1415 {
1416 handleInternalRef(parent,children);
1417 tokenizer.setStatePara();
1418 }
1419 break;
1421 {
1422 tokenizer.setStateSetScope();
1423 (void)tokenizer.lex();
1424 context.context = context.token->name;
1425 //printf("Found scope='%s'\n",qPrint(context.context));
1426 tokenizer.setStatePara();
1427 }
1428 break;
1430 handleImage(parent,children);
1431 break;
1433 tokenizer.pushState();
1434 tokenizer.setStateILine();
1435 (void)tokenizer.lex();
1436 tokenizer.popState();
1437 break;
1439 tokenizer.pushState();
1440 tokenizer.setStateIFile();
1441 (void)tokenizer.lex();
1442 tokenizer.popState();
1443 break;
1444 default:
1445 return FALSE;
1446 }
1447 break;
1448 case TokenRetval::TK_HTMLTAG:
1449 {
1450 auto handleEnterLeaveStyle = [this,&parent,&children,&tokenName](DocStyleChange::Style style) {
1451 if (!context.token->endTag)
1452 {
1453 handleStyleEnter(parent,children,style,tokenName,&context.token->attribs);
1454 }
1455 else
1456 {
1457 handleStyleLeave(parent,children,style,tokenName);
1458 }
1459 };
1460 switch (Mappers::htmlTagMapper->map(tokenName))
1461 {
1463 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <div> tag in heading");
1464 break;
1466 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <pre> tag in heading");
1467 break;
1469 handleEnterLeaveStyle(DocStyleChange::Span);
1470 break;
1472 handleEnterLeaveStyle(DocStyleChange::Bold);
1473 break;
1475 handleEnterLeaveStyle(DocStyleChange::S);
1476 break;
1478 handleEnterLeaveStyle(DocStyleChange::Strike);
1479 break;
1481 handleEnterLeaveStyle(DocStyleChange::Del);
1482 break;
1484 handleEnterLeaveStyle(DocStyleChange::Underline);
1485 break;
1487 handleEnterLeaveStyle(DocStyleChange::Ins);
1488 break;
1490 case HtmlTagType::XML_C:
1491 handleEnterLeaveStyle(DocStyleChange::Code);
1492 break;
1494 handleEnterLeaveStyle(DocStyleChange::Kbd);
1495 break;
1497 handleEnterLeaveStyle(DocStyleChange::Italic);
1498 break;
1500 handleEnterLeaveStyle(DocStyleChange::Subscript);
1501 break;
1503 handleEnterLeaveStyle(DocStyleChange::Superscript);
1504 break;
1506 handleEnterLeaveStyle(DocStyleChange::Center);
1507 break;
1509 handleEnterLeaveStyle(DocStyleChange::Small);
1510 break;
1512 handleEnterLeaveStyle(DocStyleChange::Cite);
1513 break;
1515 if (!context.token->endTag)
1516 {
1517 handleImg(parent,children,context.token->attribs);
1518 }
1519 break;
1520 default:
1521 return FALSE;
1522 break;
1523 }
1524 }
1525 break;
1526 case TokenRetval::TK_SYMBOL:
1527 {
1530 {
1531 children.append<DocSymbol>(this,parent,s);
1532 }
1533 else
1534 {
1535 return FALSE;
1536 }
1537 }
1538 break;
1539 case TokenRetval::TK_WHITESPACE:
1540 case TokenRetval::TK_NEWPARA:
1541handlepara:
1542 if (insidePRE(parent) || !children.empty())
1543 {
1544 children.append<DocWhiteSpace>(this,parent,context.token->chars);
1545 }
1546 break;
1547 case TokenRetval::TK_LNKWORD:
1548 if (handleWord)
1549 {
1550 handleLinkedWord(parent,children);
1551 }
1552 else
1553 return FALSE;
1554 break;
1555 case TokenRetval::TK_WORD:
1556 if (handleWord)
1557 {
1558 children.append<DocWord>(this,parent,context.token->name);
1559 }
1560 else
1561 return FALSE;
1562 break;
1563 case TokenRetval::TK_URL:
1564 if (context.insideHtmlLink)
1565 {
1566 children.append<DocWord>(this,parent,context.token->name);
1567 }
1568 else
1569 {
1570 children.append<DocURL>(this,parent,context.token->name,context.token->isEMailAddr);
1571 }
1572 break;
1573 default:
1574 return FALSE;
1575 }
1576 return TRUE;
1577}
1578
1579//---------------------------------------------------------------------------
1580
1582{
1583 AUTO_TRACE();
1584 bool found=FALSE;
1585 size_t index=0;
1586 for (const auto &opt : tagHtmlAttribs)
1587 {
1588 AUTO_TRACE_ADD("option name={} value='{}'",opt.name,opt.value);
1589 if (opt.name=="src" && !opt.value.isEmpty())
1590 {
1591 // copy attributes
1592 HtmlAttribList attrList = tagHtmlAttribs;
1593 // and remove the src attribute
1594 attrList.erase(attrList.begin()+index);
1596 children.append<DocImage>(
1597 this,parent,attrList,
1598 findAndCopyImage(opt.value,t,false),
1599 t,opt.value);
1600 found = TRUE;
1601 }
1602 ++index;
1603 }
1604 if (!found)
1605 {
1606 warn_doc_error(context.fileName,tokenizer.getLineNr(),"IMG tag does not have a SRC attribute!");
1607 }
1608}
1609
1610//---------------------------------------------------------------------------
1611
1613 const QCString &doc)
1614{
1615 AUTO_TRACE();
1616 Token retval = Token::make_RetVal_OK();
1617
1618 if (doc.isEmpty()) return retval;
1619
1620 tokenizer.init(doc.data(),context.fileName,context.markdownSupport,context.insideHtmlLink);
1621
1622 // first parse any number of paragraphs
1623 bool isFirst=TRUE;
1624 DocPara *lastPar=!children.empty() ? std::get_if<DocPara>(&children.back()): nullptr;
1625 if (lastPar)
1626 { // last child item was a paragraph
1627 isFirst=FALSE;
1628 }
1629 do
1630 {
1631 children.append<DocPara>(this,parent);
1632 DocPara *par = children.get_last<DocPara>();
1633 if (isFirst) { par->markFirst(); isFirst=FALSE; }
1634 retval=par->parse();
1635 if (!par->isEmpty())
1636 {
1637 if (lastPar) lastPar->markLast(FALSE);
1638 lastPar=par;
1639 }
1640 else
1641 {
1642 children.pop_back();
1643 }
1644 } while (retval.is(TokenRetval::TK_NEWPARA));
1645 if (lastPar) lastPar->markLast();
1646
1647 AUTO_TRACE_EXIT("isFirst={} isLast={}",lastPar?lastPar->isFirst():-1,lastPar?lastPar->isLast():-1);
1648 return retval;
1649}
1650
1651//---------------------------------------------------------------------------
1652
1654{
1655 AUTO_TRACE("file={} text={}",file,text);
1656 bool ambig = false;
1657 QCString filePath = findFilePath(file,ambig);
1658 if (!filePath.isEmpty())
1659 {
1660 text = fileToString(filePath,Config_getBool(FILTER_SOURCE_FILES));
1661 if (ambig)
1662 {
1663 warn_doc_error(context.fileName,tokenizer.getLineNr(),"included file name '{}' is ambiguous"
1664 "Possible candidates:\n{}",file, showFileDefMatches(Doxygen::exampleNameLinkedMap,file));
1665 }
1666 }
1667 else
1668 {
1669 warn_doc_error(context.fileName,tokenizer.getLineNr(),"included file '{}' is not found. "
1670 "Check your EXAMPLE_PATH",file);
1671 }
1672}
1673
1674//---------------------------------------------------------------------------
1675
1676static QCString extractCopyDocId(const char *data, size_t &j, size_t len)
1677{
1678 size_t s=j;
1679 int round=0;
1680 bool insideDQuote=FALSE;
1681 bool insideSQuote=FALSE;
1682 bool found=FALSE;
1683 while (j<len && !found)
1684 {
1685 if (!insideSQuote && !insideDQuote)
1686 {
1687 switch (data[j])
1688 {
1689 case '(': round++; break;
1690 case ')': round--; break;
1691 case '"': insideDQuote=TRUE; break;
1692 case '\'': insideSQuote=TRUE; break;
1693 case '\\': // fall through, begin of command
1694 case '@': // fall through, begin of command
1695 case '\t': // fall through
1696 case '\n':
1697 found=(round==0);
1698 break;
1699 case ' ': // allow spaces in cast operator (see issue #11169)
1700 found=(round==0) && (j<8 || !literal_at(data+j-8,"operator"));
1701 break;
1702 }
1703 }
1704 else if (insideSQuote) // look for single quote end
1705 {
1706 if (data[j]=='\'' && (j==0 || data[j]!='\\'))
1707 {
1708 insideSQuote=FALSE;
1709 }
1710 }
1711 else if (insideDQuote) // look for double quote end
1712 {
1713 if (data[j]=='"' && (j==0 || data[j]!='\\'))
1714 {
1715 insideDQuote=FALSE;
1716 }
1717 }
1718 if (!found) j++;
1719 }
1720
1721 // include const and volatile
1722 if (literal_at(data+j," const"))
1723 {
1724 j+=6;
1725 }
1726 else if (literal_at(data+j," volatile"))
1727 {
1728 j+=9;
1729 }
1730
1731 // allow '&' or '&&' or ' &' or ' &&' at the end
1732 size_t k=j;
1733 while (k<len && data[k]==' ') k++;
1734 if (k<len-1 && data[k]=='&' && data[k+1]=='&') j=k+2;
1735 else if (k<len && data[k]=='&' ) j=k+1;
1736
1737 // do not include punctuation added by Definition::_setBriefDescription()
1738 size_t e=j;
1739 if (j>0 && data[j-1]=='.') { e--; }
1740 QCString id(data+s,e-s);
1741 //printf("extractCopyDocId='%s' input='%s'\n",qPrint(id),&data[s]);
1742 return id;
1743}
1744
1745// macro to check if the input starts with a specific command.
1746// note that data[i] should point to the start of the command (\ or @ character)
1747// and the sizeof(str) returns the size of str including the '\0' terminator;
1748// a fact we abuse to skip over the start of the command character.
1749#define CHECK_FOR_COMMAND(str,action) \
1750 do if ((i+sizeof(str)<len) && literal_at(data+i+1,str)) \
1751 { j=i+sizeof(str); action; } while(0)
1752
1753static size_t isCopyBriefOrDetailsCmd(const char *data, size_t i,size_t len,bool &brief)
1754{
1755 size_t j=0;
1756 if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1757 {
1758 CHECK_FOR_COMMAND("copybrief",brief=TRUE); // @copybrief or \copybrief
1759 CHECK_FOR_COMMAND("copydetails",brief=FALSE); // @copydetails or \copydetails
1760 }
1761 return j;
1762}
1763
1764static size_t isVerbatimSection(const char *data,size_t i,size_t len,QCString &endMarker)
1765{
1766 size_t j=0;
1767 if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1768 {
1769 CHECK_FOR_COMMAND("dot",endMarker="enddot");
1770 CHECK_FOR_COMMAND("icode",endMarker="endicode");
1771 CHECK_FOR_COMMAND("code",endMarker="endcode");
1772 CHECK_FOR_COMMAND("msc",endMarker="endmsc");
1773 CHECK_FOR_COMMAND("iverbatim",endMarker="endiverbatim");
1774 CHECK_FOR_COMMAND("verbatim",endMarker="endverbatim");
1775 CHECK_FOR_COMMAND("iliteral",endMarker="endiliteral");
1776 CHECK_FOR_COMMAND("latexonly",endMarker="endlatexonly");
1777 CHECK_FOR_COMMAND("htmlonly",endMarker="endhtmlonly");
1778 CHECK_FOR_COMMAND("xmlonly",endMarker="endxmlonly");
1779 CHECK_FOR_COMMAND("rtfonly",endMarker="endrtfonly");
1780 CHECK_FOR_COMMAND("manonly",endMarker="endmanonly");
1781 CHECK_FOR_COMMAND("docbookonly",endMarker="enddocbookonly");
1782 CHECK_FOR_COMMAND("startuml",endMarker="enduml");
1783 }
1784 //printf("isVerbatimSection(%s)=%d)\n",qPrint(QCString(&data[i]).left(10)),j);
1785 return j;
1786}
1787
1788static size_t skipToEndMarker(const char *data,size_t i,size_t len,const QCString &endMarker)
1789{
1790 while (i<len)
1791 {
1792 if ((data[i]=='@' || data[i]=='\\') && // start of command character
1793 (i==0 || (data[i-1]!='@' && data[i-1]!='\\'))) // that is not escaped
1794 {
1795 if (i+endMarker.length()+1<=len && qstrncmp(data+i+1,endMarker.data(),endMarker.length())==0)
1796 {
1797 return i+endMarker.length()+1;
1798 }
1799 }
1800 i++;
1801 }
1802 // oops no endmarker found...
1803 return i<len ? i+1 : len;
1804}
1805
1806
1807QCString DocParser::processCopyDoc(const char *data,size_t &len)
1808{
1809 AUTO_TRACE("data={} len={}",Trace::trunc(data),len);
1810 GrowBuf buf;
1811 size_t i=0;
1812 int lineNr = tokenizer.getLineNr();
1813 while (i<len)
1814 {
1815 char c = data[i];
1816 if (c=='@' || c=='\\') // look for a command
1817 {
1818 bool isBrief=TRUE;
1819 size_t j=isCopyBriefOrDetailsCmd(data,i,len,isBrief);
1820 if (j>0)
1821 {
1822 // skip whitespace
1823 while (j<len && (data[j]==' ' || data[j]=='\t')) j++;
1824 // extract the argument
1825 QCString id = extractCopyDocId(data,j,len);
1826 const Definition *def = nullptr;
1827 QCString doc,brief;
1828 //printf("resolving docs='%s'\n",qPrint(id));
1829 bool found = findDocsForMemberOrCompound(id,&doc,&brief,&def);
1830 if (found && def->isReference())
1831 {
1832 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1833 "@copy{} or @copydoc target '{}' found but is from a tag file, skipped",
1834 isBrief?"brief":"details", id);
1835 }
1836 else if (found)
1837 {
1838 //printf("found it def=%p brief='%s' doc='%s' isBrief=%d\n",def,qPrint(brief),qPrint(doc),isBrief);
1839 auto it = std::find(context.copyStack.begin(),context.copyStack.end(),def);
1840 if (it==context.copyStack.end()) // definition not parsed earlier
1841 {
1842 QCString orgFileName = context.fileName;
1843 context.copyStack.push_back(def);
1844 auto addDocs = [&](const QCString &file_,int line_,const QCString &doc_)
1845 {
1846 buf.addStr(" \\ifile \""+file_+"\" ");
1847 buf.addStr("\\iline "+QCString().setNum(line_)+" \\ilinebr ");
1848 size_t len_ = doc_.length();
1849 buf.addStr(processCopyDoc(doc_.data(),len_));
1850 };
1851 if (isBrief)
1852 {
1853 addDocs(def->briefFile(),def->briefLine(),brief);
1854 }
1855 else
1856 {
1857 addDocs(def->docFile(),def->docLine(),doc);
1859 {
1860 const MemberDef *md = toMemberDef(def);
1861 const ArgumentList &docArgList = md->templateMaster() ?
1862 md->templateMaster()->argumentList() :
1863 md->argumentList();
1864 buf.addStr(inlineArgListToDoc(docArgList));
1865 }
1866 }
1867 context.copyStack.pop_back();
1868 buf.addStr(" \\ilinebr \\ifile \""+context.fileName+"\" ");
1869 buf.addStr("\\iline "+QCString().setNum(lineNr)+" ");
1870 }
1871 else
1872 {
1873 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1874 "Found recursive @copy{} or @copydoc relation for argument '{}'.",
1875 isBrief?"brief":"details",id);
1876 }
1877 }
1878 else
1879 {
1880 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1881 "@copy{} or @copydoc target '{}' not found", isBrief?"brief":"details",id);
1882 }
1883 // skip over command
1884 i=j;
1885 }
1886 else
1887 {
1888 QCString endMarker;
1889 size_t k = isVerbatimSection(data,i,len,endMarker);
1890 if (k>0)
1891 {
1892 size_t orgPos = i;
1893 i=skipToEndMarker(data,k,len,endMarker);
1894 buf.addStr(data+orgPos,i-orgPos);
1895 // TODO: adjust lineNr
1896 }
1897 else
1898 {
1899 buf.addChar(c);
1900 i++;
1901 }
1902 }
1903 }
1904 else // not a command, just copy
1905 {
1906 buf.addChar(c);
1907 i++;
1908 lineNr += (c=='\n') ? 1 : 0;
1909 }
1910 }
1911 len = buf.getPos();
1912 buf.addChar(0);
1913 AUTO_TRACE_EXIT("result={}",Trace::trunc(buf.get()));
1914 return buf.get();
1915}
1916
1917
1918//---------------------------------------------------------------------------
1919
1921 const QCString &fileName,int startLine,
1922 const Definition *ctx,const MemberDef *md,
1923 const QCString &input,bool indexWords,
1924 bool isExample, const QCString &exampleName,
1925 bool singleLine, bool linkFromIndex,
1926 bool markdownSupport)
1927{
1928 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
1929 assert(parser!=nullptr);
1930 if (parser==nullptr) return nullptr;
1931 //printf("validatingParseDoc(%s,%s)=[%s]\n",ctx?qPrint(ctx->name()):"<none>",
1932 // md?qPrint(md->name()):"<none>",
1933 // input);
1934 //printf("========== validating %s at line %d\n",qPrint(fileName),startLine);
1935 //printf("---------------- input --------------------\n%s\n----------- end input -------------------\n",qPrint(input));
1936
1937 // set initial token
1938 parser->context.token = parser->tokenizer.resetToken();
1939
1940 if (ctx && ctx!=Doxygen::globalScope &&
1943 )
1944 )
1945 {
1947 }
1948 else if (ctx && ctx->definitionType()==Definition::TypePage)
1949 {
1950 const Definition *scope = (toPageDef(ctx))->getPageScope();
1951 if (scope && scope!=Doxygen::globalScope)
1952 {
1953 parser->context.context = substitute(scope->name(),getLanguageSpecificSeparator(scope->getLanguage(),true),"::");
1954 }
1955 }
1956 else if (ctx && ctx->definitionType()==Definition::TypeGroup)
1957 {
1958 const Definition *scope = (toGroupDef(ctx))->getGroupScope();
1959 if (scope && scope!=Doxygen::globalScope)
1960 {
1961 parser->context.context = substitute(scope->name(),getLanguageSpecificSeparator(scope->getLanguage(),true),"::");
1962 }
1963 }
1964 else
1965 {
1966 parser->context.context = "";
1967 }
1968 parser->context.scope = ctx;
1969 parser->context.lang = getLanguageFromFileName(fileName);
1970
1971 if (indexWords && Doxygen::searchIndex.enabled())
1972 {
1973 if (md)
1974 {
1975 parser->context.searchUrl=md->getOutputFileBase();
1976 Doxygen::searchIndex.setCurrentDoc(md,md->anchor(),false);
1977 }
1978 else if (ctx)
1979 {
1980 parser->context.searchUrl=ctx->getOutputFileBase();
1981 Doxygen::searchIndex.setCurrentDoc(ctx,ctx->anchor(),false);
1982 }
1983 }
1984 else
1985 {
1986 parser->context.searchUrl="";
1987 }
1988
1989 parser->context.fileName = fileName;
1990 parser->context.relPath = (!linkFromIndex && ctx) ?
1992 QCString("");
1993 //printf("ctx->name=%s relPath=%s\n",qPrint(ctx->name()),qPrint(parser->context.relPath));
1994 parser->context.memberDef = md;
1995 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
1996 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
1997 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
1998 parser->context.inSeeBlock = FALSE;
1999 parser->context.xmlComment = FALSE;
2000 parser->context.insideHtmlLink = FALSE;
2001 parser->context.includeFileText = "";
2002 parser->context.includeFileOffset = 0;
2003 parser->context.includeFileLength = 0;
2004 parser->context.isExample = isExample;
2005 parser->context.exampleName = exampleName;
2006 parser->context.hasParamCommand = FALSE;
2007 parser->context.hasReturnCommand = FALSE;
2008 parser->context.retvalsFound.clear();
2009 parser->context.paramsFound.clear();
2010 parser->context.markdownSupport = markdownSupport;
2011
2012 //printf("Starting comment block at %s:%d\n",qPrint(parser->context.fileName),startLine);
2013 parser->tokenizer.setLineNr(startLine);
2014 size_t ioLen = input.length();
2015 QCString inpStr = parser->processCopyDoc(input.data(),ioLen);
2016 if (inpStr.isEmpty() || inpStr.at(inpStr.length()-1)!='\n')
2017 {
2018 inpStr+='\n';
2019 }
2020 //printf("processCopyDoc(in='%s' out='%s')\n",input,qPrint(inpStr));
2021 parser->tokenizer.init(inpStr.data(),parser->context.fileName,
2023
2024 // build abstract syntax tree
2025 auto ast = std::make_unique<DocNodeAST>(DocRoot(parser,md!=nullptr,singleLine));
2026 std::get<DocRoot>(ast->root).parse();
2027
2029 {
2030 // pretty print the result
2031 std::visit(PrintDocVisitor{},ast->root);
2032 }
2033
2034 if (md && md->isFunction())
2035 {
2037 }
2039
2040 // reset token
2041 parser->tokenizer.resetToken();
2042
2043 //printf(">>>>>> end validatingParseDoc(%s,%s)\n",ctx?qPrint(ctx->name()):"<none>",
2044 // md?qPrint(md->name()):"<none>");
2045
2046 return ast;
2047}
2048
2050{
2051 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2052 assert(parser!=nullptr);
2053 if (parser==nullptr) return nullptr;
2054
2055 // set initial token
2056 parser->context.token = parser->tokenizer.resetToken();
2057
2058 //printf("------------ input ---------\n%s\n"
2059 // "------------ end input -----\n",input);
2060 //parser->context.token = new TokenInfo;
2061 parser->context.context = "";
2062 parser->context.fileName = "<parseText>";
2063 parser->context.relPath = "";
2064 parser->context.memberDef = nullptr;
2065 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
2066 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
2067 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
2068 parser->context.inSeeBlock = FALSE;
2069 parser->context.xmlComment = FALSE;
2070 parser->context.insideHtmlLink = FALSE;
2071 parser->context.includeFileText = "";
2072 parser->context.includeFileOffset = 0;
2073 parser->context.includeFileLength = 0;
2074 parser->context.isExample = FALSE;
2075 parser->context.exampleName = "";
2076 parser->context.hasParamCommand = FALSE;
2077 parser->context.hasReturnCommand = FALSE;
2078 parser->context.retvalsFound.clear();
2079 parser->context.paramsFound.clear();
2080 parser->context.searchUrl="";
2082 parser->context.markdownSupport = Config_getBool(MARKDOWN_SUPPORT);
2083
2084
2085 auto ast = std::make_unique<DocNodeAST>(DocText(parser));
2086
2087 if (!input.isEmpty())
2088 {
2089 parser->tokenizer.setLineNr(1);
2090 parser->tokenizer.init(input.data(),parser->context.fileName,
2092
2093 // build abstract syntax tree
2094 std::get<DocText>(ast->root).parse();
2095
2097 {
2098 // pretty print the result
2099 std::visit(PrintDocVisitor{},ast->root);
2100 }
2101 }
2102
2103 return ast;
2104}
2105
2106IDocNodeASTPtr createRef(IDocParser &parserIntf,const QCString &target,const QCString &context, const QCString &srcFile, int srcLine )
2107{
2108 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2109 assert(parser!=nullptr);
2110 if (parser==nullptr) return nullptr;
2111 if (!srcFile.isEmpty())
2112 {
2113 parser->context.fileName = srcFile;
2114 parser->tokenizer.setLineNr(srcLine);
2115 }
2116 return std::make_unique<DocNodeAST>(DocRef(parser,nullptr,target,context));
2117}
2118
2119void docFindSections(const QCString &input,
2120 const Definition *d,
2121 const QCString &fileName)
2122{
2123 DocParser parser;
2124 parser.tokenizer.findSections(input,d,fileName);
2125}
2126
This class represents an function or template argument list.
Definition arguments.h:65
size_t size() const
Definition arguments.h:100
void push_back(const Argument &a)
Definition arguments.h:102
bool empty() const
Definition arguments.h:99
A abstract class representing of a compound symbol.
Definition classdef.h:104
@ PrintTree
Definition debug.h:34
static bool isFlagSet(const DebugMask mask)
Definition debug.cpp:132
The common base class of all entity definitions found in the sources.
Definition definition.h:76
virtual QCString docFile() const =0
virtual SrcLangExt getLanguage() const =0
Returns the programming language this definition was written in.
virtual int docLine() const =0
virtual bool isLinkable() const =0
virtual DefType definitionType() const =0
virtual QCString anchor() const =0
virtual QCString briefDescriptionAsTooltip() const =0
virtual int briefLine() const =0
virtual QCString briefDescription(bool abbreviate=FALSE) const =0
virtual QCString getReference() const =0
virtual QCString getSourceFileBase() const =0
virtual QCString documentation() const =0
virtual QCString qualifiedName() const =0
virtual QCString briefFile() const =0
virtual QCString getOutputFileBase() const =0
virtual bool isReference() const =0
virtual const QCString & name() const =0
Class representing a directory in the file system.
Definition dir.h:75
bool remove(const std::string &path, bool acceptsAbsPath=true) const
Definition dir.cpp:314
Node representing an anchor.
Definition docnode.h:228
Node representing an item of a cross-referenced list.
Definition docnode.h:524
Node representing a Hypertext reference.
Definition docnode.h:817
Node representing an image.
Definition docnode.h:636
@ DocBook
Definition docnode.h:638
Node representing an internal reference to some item.
Definition docnode.h:801
Node representing a word that can be linked to something.
Definition docnode.h:164
Node representing a paragraph in the documentation tree.
Definition docnode.h:1074
bool isLast() const
Definition docnode.h:1082
void markLast(bool v=TRUE)
Definition docnode.h:1080
bool isFirst() const
Definition docnode.h:1081
bool defaultHandleToken(DocNodeVariant *parent, Token tok, DocNodeList &children, bool handleWord=TRUE)
std::stack< DocParserContext > contextStack
void handleLinkedWord(DocNodeVariant *parent, DocNodeList &children, bool ignoreAutoLinkFlag=FALSE)
DocTokenizer tokenizer
void handleInternalRef(DocNodeVariant *parent, DocNodeList &children)
void handleParameterType(DocNodeVariant *parent, DocNodeList &children, const QCString &paramTypes)
QCString findAndCopyImage(const QCString &fileName, DocImage::Type type, bool doWarn=true)
Definition docparser.cpp:94
void checkRetvalName()
QCString processCopyDoc(const char *data, size_t &len)
void readTextFileByName(const QCString &file, QCString &text)
Token handleAHref(DocNodeVariant *parent, DocNodeList &children, const HtmlAttribList &tagHtmlAttribs)
Token internalValidatingParseDoc(DocNodeVariant *parent, DocNodeList &children, const QCString &doc)
void handleInitialStyleCommands(DocNodeVariant *parent, DocNodeList &children)
void handleStyleLeave(DocNodeVariant *parent, DocNodeList &children, DocStyleChange::Style s, const QCString &tagName)
void handlePendingStyleCommands(DocNodeVariant *parent, DocNodeList &children)
void checkUnOrMultipleDocumentedParams()
void popContext()
Definition docparser.cpp:74
bool findDocsForMemberOrCompound(const QCString &commandName, QCString *pDoc, QCString *pBrief, const Definition **pDef)
void handleImage(DocNodeVariant *parent, DocNodeList &children)
void handleStyleEnter(DocNodeVariant *parent, DocNodeList &children, DocStyleChange::Style s, const QCString &tagName, const HtmlAttribList *attribs)
void handlePrefix(DocNodeVariant *parent, DocNodeList &children)
Token handleStyleArgument(DocNodeVariant *parent, DocNodeList &children, const QCString &cmdName)
void checkArgumentName()
DocParserContext context
void handleAnchor(DocNodeVariant *parent, DocNodeList &children)
void handleImg(DocNodeVariant *parent, DocNodeList &children, const HtmlAttribList &tagHtmlAttribs)
void defaultHandleTitleAndSize(const CommandType cmd, DocNodeVariant *parent, DocNodeList &children, QCString &width, QCString &height)
void handleUnclosedStyleCommands()
void pushContext()
Definition docparser.cpp:60
void errorHandleDefaultToken(DocNodeVariant *parent, Token tok, DocNodeList &children, const QCString &txt)
Node representing a reference to some item.
Definition docnode.h:772
Root node of documentation tree.
Definition docnode.h:1307
Node representing a separator.
Definition docnode.h:360
Node representing a style change.
Definition docnode.h:264
const HtmlAttribList & attribs() const
Definition docnode.h:306
QCString tagName() const
Definition docnode.h:307
Style style() const
Definition docnode.h:302
size_t position() const
Definition docnode.h:305
Node representing a special symbol.
Definition docnode.h:323
static HtmlEntityMapper::SymType decodeSymbol(const QCString &symName)
Definition docnode.cpp:152
Root node of a text fragment.
Definition docnode.h:1298
void init(const char *input, const QCString &fileName, bool markdownSupport, bool insideHtmlLink)
void setLineNr(int lineno)
void findSections(const QCString &input, const Definition *d, const QCString &fileName)
TokenInfo * resetToken()
Node representing a URL (or email address)
Definition docnode.h:187
Node representing a verbatim, unparsed text fragment.
Definition docnode.h:371
Node representing some amount of white space.
Definition docnode.h:349
Node representing a word.
Definition docnode.h:152
static NamespaceLinkedMap * namespaceLinkedMap
Definition doxygen.h:115
static FileNameLinkedMap * inputNameLinkedMap
Definition doxygen.h:105
static ClassLinkedMap * classLinkedMap
Definition doxygen.h:96
static NamespaceDefMutable * globalScope
Definition doxygen.h:121
static FileNameLinkedMap * imageNameLinkedMap
Definition doxygen.h:106
static IndexList * indexList
Definition doxygen.h:134
static PageLinkedMap * pageLinkedMap
Definition doxygen.h:100
static FileNameLinkedMap * exampleNameLinkedMap
Definition doxygen.h:103
static SearchIndexIntf searchIndex
Definition doxygen.h:124
static GroupLinkedMap * groupLinkedMap
Definition doxygen.h:114
A model of a file symbol.
Definition filedef.h:99
virtual QCString absFilePath() const =0
Minimal replacement for QFileInfo.
Definition fileinfo.h:23
bool isSymLink() const
Definition fileinfo.cpp:77
bool exists() const
Definition fileinfo.cpp:30
A model of a group of symbols.
Definition groupdef.h:52
virtual QCString groupTitle() const =0
Class representing a string buffer optimized for growing.
Definition growbuf.h:28
size_t getPos() const
Definition growbuf.h:116
void addChar(char c)
Definition growbuf.h:69
void addStr(const QCString &s)
Definition growbuf.h:72
char * get()
Definition growbuf.h:114
T & back()
access the last element
Definition growvector.h:135
void pop_back()
removes the last element
Definition growvector.h:115
bool empty() const
checks whether the container is empty
Definition growvector.h:140
Class representing a list of HTML attributes.
Definition htmlattrib.h:33
opaque parser interface
Definition docparser.h:34
A model of a class/file/namespace member symbol.
Definition memberdef.h:48
virtual bool isObjCMethod() const =0
virtual const ClassDef * getClassDef() const =0
virtual const ArgumentList & argumentList() const =0
virtual bool isFunction() const =0
virtual QCString objCMethodName(bool localLink, bool showStatic) const =0
virtual const MemberDef * templateMaster() const =0
virtual void detectUndocumentedParams(bool hasParamCommand, bool hasReturnCommand) const =0
An abstract interface of a namespace symbol.
A model of a page symbol.
Definition pagedef.h:26
This is an alternative implementation of QCString.
Definition qcstring.h:101
int find(char c, int index=0, bool cs=TRUE) const
Definition qcstring.cpp:43
QCString & prepend(const char *s)
Definition qcstring.h:407
size_t length() const
Returns the length of the string, not counting the 0-terminator.
Definition qcstring.h:153
bool startsWith(const char *s) const
Definition qcstring.h:492
QCString mid(size_t index, size_t len=static_cast< size_t >(-1)) const
Definition qcstring.h:226
QCString lower() const
Definition qcstring.h:234
bool endsWith(const char *s) const
Definition qcstring.h:509
char & at(size_t i)
Returns a reference to the character at index i.
Definition qcstring.h:578
bool isEmpty() const
Returns TRUE iff the string is empty.
Definition qcstring.h:150
QCString stripWhiteSpace() const
returns a copy of this string with leading and trailing whitespace removed
Definition qcstring.h:245
const std::string & str() const
Definition qcstring.h:537
QCString right(size_t len) const
Definition qcstring.h:219
size_t size() const
Returns the length of the string, not counting the 0-terminator.
Definition qcstring.h:156
QCString & sprintf(const char *format,...)
Definition qcstring.cpp:29
@ ExplicitSize
Definition qcstring.h:133
int findRev(char c, int index=-1, bool cs=TRUE) const
Definition qcstring.cpp:91
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
Definition qcstring.h:159
QCString left(size_t len) const
Definition qcstring.h:214
bool is(TokenRetval rv) const
TOKEN_SPECIFICATIONS RETVAL_SPECIFICATIONS const char * to_string() const
TokenRetval value() const
bool is_any_of(ARGS... args) const
char command_to_char() const
ClassDef * getClass(const QCString &n)
Class representing a regular expression.
Definition regex.h:39
Iterator class to iterator through matches.
Definition regex.h:232
CommandType
Definition cmdmapper.h:29
@ CMD_INTERNALREF
Definition cmdmapper.h:65
#define Config_getList(name)
Definition config.h:38
#define Config_getBool(name)
Definition config.h:33
#define Config_getString(name)
Definition config.h:32
std::vector< std::string > StringVector
Definition containers.h:33
DirIterator end(const DirIterator &) noexcept
Definition dir.cpp:175
#define AUTO_TRACE_ADD(...)
Definition docnode.cpp:47
#define AUTO_TRACE(...)
Definition docnode.cpp:46
#define AUTO_TRACE_EXIT(...)
Definition docnode.cpp:48
std::variant< DocWord, DocLinkedWord, DocURL, DocLineBreak, DocHorRuler, DocAnchor, DocCite, DocStyleChange, DocSymbol, DocEmoji, DocWhiteSpace, DocSeparator, DocVerbatim, DocInclude, DocIncOperator, DocFormula, DocIndexEntry, DocAutoList, DocAutoListItem, DocTitle, DocXRefItem, DocImage, DocDotFile, DocMscFile, DocDiaFile, DocVhdlFlow, DocLink, DocRef, DocInternalRef, DocHRef, DocHtmlHeader, DocHtmlDescTitle, DocHtmlDescList, DocSection, DocSecRefItem, DocSecRefList, DocInternal, DocParBlock, DocSimpleList, DocHtmlList, DocSimpleSect, DocSimpleSectSep, DocParamSect, DocPara, DocParamList, DocSimpleListItem, DocHtmlListItem, DocHtmlDescData, DocHtmlCell, DocHtmlCaption, DocHtmlRow, DocHtmlTable, DocHtmlBlockQuote, DocText, DocRoot, DocHtmlDetails, DocHtmlSummary, DocPlantUmlFile > DocNodeVariant
Definition docnode.h:66
constexpr DocNodeVariant * parent(DocNodeVariant *n)
returns the parent node of a given node n or nullptr if the node has no parent.
Definition docnode.h:1324
void docFindSections(const QCString &input, const Definition *d, const QCString &fileName)
static QCString extractCopyDocId(const char *data, size_t &j, size_t len)
static size_t skipToEndMarker(const char *data, size_t i, size_t len, const QCString &endMarker)
IDocNodeASTPtr validatingParseText(IDocParser &parserIntf, const QCString &input)
IDocParserPtr createDocParser()
factory function to create a parser
Definition docparser.cpp:55
IDocNodeASTPtr createRef(IDocParser &parserIntf, const QCString &target, const QCString &context, const QCString &srcFile, int srcLine)
static size_t isVerbatimSection(const char *data, size_t i, size_t len, QCString &endMarker)
IDocNodeASTPtr validatingParseDoc(IDocParser &parserIntf, const QCString &fileName, int startLine, const Definition *ctx, const MemberDef *md, const QCString &input, bool indexWords, bool isExample, const QCString &exampleName, bool singleLine, bool linkFromIndex, bool markdownSupport)
static size_t isCopyBriefOrDetailsCmd(const char *data, size_t i, size_t len, bool &brief)
#define CHECK_FOR_COMMAND(str, action)
std::unique_ptr< IDocNodeAST > IDocNodeASTPtr
Definition docparser.h:56
std::unique_ptr< IDocParser > IDocParserPtr
pointer to parser interface
Definition docparser.h:40
Private header shared between docparser.cpp and docnode.cpp.
IterableStack< const DocNodeVariant * > DocStyleChangeStack
Definition docparser_p.h:55
bool insidePRE(const DocNodeVariant *n)
bool insideLI(const DocNodeVariant *n)
FileDef * toFileDef(Definition *d)
Definition filedef.cpp:1894
GroupDef * toGroupDef(Definition *d)
MemberDef * toMemberDef(Definition *d)
#define warn_incomplete_doc(file, line, fmt,...)
Definition message.h:107
#define err(fmt,...)
Definition message.h:127
#define warn_doc_error(file, line, fmt,...)
Definition message.h:112
const Mapper< HtmlTagType > * htmlTagMapper
const Mapper< CommandType > * cmdMapper
int system(const QCString &command, const QCString &args, bool commandHasConsole=true)
Definition portable.cpp:106
QCString trunc(const QCString &s, size_t numChars=15)
Definition trace.h:56
bool match(std::string_view str, Match &match, const Ex &re)
Matches a given string str for a match against regular expression re.
Definition regex.cpp:759
PageDef * toPageDef(Definition *d)
Definition pagedef.cpp:467
Portable versions of functions that are platform dependent.
QCString substitute(const QCString &s, const QCString &src, const QCString &dst)
substitute all occurrences of src in s by dst
Definition qcstring.cpp:477
int qstrncmp(const char *str1, const char *str2, size_t len)
Definition qcstring.h:75
const char * qPrint(const char *s)
Definition qcstring.h:672
#define TRUE
Definition qcstring.h:37
#define FALSE
Definition qcstring.h:34
uint32_t qstrlen(const char *str)
Returns the length of string str, or 0 if a null pointer is passed.
Definition qcstring.h:58
Some helper functions for std::string.
bool literal_at(const char *data, const char(&str)[N])
returns TRUE iff data points to a substring that matches string literal str
Definition stringutil.h:98
This class contains the information about the argument of a function or template.
Definition arguments.h:27
void append(Args &&... args)
Append a new DocNodeVariant to the list by constructing it with type T and parameters Args.
Definition docnode.h:1393
T * get_last()
Returns a pointer to the last element in the list if that element exists and holds a T,...
Definition docnode.h:1404
StringMultiSet retvalsFound
Definition docparser_p.h:75
DocStyleChangeStack styleStack
Definition docparser_p.h:67
size_t includeFileLength
Definition docparser_p.h:87
QCString fileName
Definition docparser_p.h:70
DocNodeStack nodeStack
Definition docparser_p.h:66
StringMultiSet paramsFound
Definition docparser_p.h:76
QCString exampleName
Definition docparser_p.h:79
const Definition * scope
Definition docparser_p.h:61
QCString includeFileText
Definition docparser_p.h:85
TokenInfo * token
Definition docparser_p.h:92
DocStyleChangeStack initialStyleStack
Definition docparser_p.h:68
SrcLangExt lang
Definition docparser_p.h:82
QCString searchUrl
Definition docparser_p.h:80
size_t includeFileOffset
Definition docparser_p.h:86
const MemberDef * memberDef
Definition docparser_p.h:77
bool checkCV
Definition util.h:118
const MemberDef * md
Definition util.h:125
bool found
Definition util.h:124
SrcLangExt
Language as given by extension.
Definition types.h:42
@ Fortran
Definition types.h:53
@ Unknown
Definition types.h:43
@ Python
Definition types.h:52
QCString removeRedundantWhiteSpace(const QCString &s)
Definition util.cpp:578
QCString findFilePath(const QCString &file, bool &ambig)
Definition util.cpp:3491
QCString linkToText(SrcLangExt lang, const QCString &link, bool isFileName)
Definition util.cpp:3219
SrcLangExt getLanguageFromFileName(const QCString &fileName, SrcLangExt defLang)
Definition util.cpp:5705
bool resolveRef(const QCString &scName, const QCString &name, bool inSeeBlock, const Definition **resContext, const MemberDef **resMember, SrcLangExt lang, bool lookForSpecialization, const FileDef *currentFile, bool checkScope)
Definition util.cpp:2964
QCString relativePathToRoot(const QCString &name)
Definition util.cpp:4079
QCString showFileDefMatches(const FileNameLinkedMap *fnMap, const QCString &n)
Definition util.cpp:3534
bool found
Definition util.cpp:984
QCString fileToString(const QCString &name, bool filter, bool isSourceCode)
Definition util.cpp:1441
QCString convertNameToFile(const QCString &name, bool allowDots, bool allowUnderscore)
Definition util.cpp:4004
QCString argListToString(const ArgumentList &al, bool useCanonicalType, bool showDefVals)
Definition util.cpp:1201
QCString getLanguageSpecificSeparator(SrcLangExt lang, bool classScope)
Returns the scope separator to use given the programming language lang.
Definition util.cpp:6386
GetDefResult getDefs(const GetDefInput &input)
Definition util.cpp:2822
StringVector split(const std::string &s, const std::string &delimiter)
split input string s by string delimiter delimiter.
Definition util.cpp:7105
bool copyFile(const QCString &src, const QCString &dest)
Copies the contents of file with name src to the newly created file with name dest.
Definition util.cpp:6326
QCString inlineArgListToDoc(const ArgumentList &al)
Definition util.cpp:1156
FileDef * findFileDef(const FileNameLinkedMap *fnMap, const QCString &n, bool &ambig)
Definition util.cpp:3408
A bunch of utility functions.