1 /******************************************************************************
3 * Copyright (C) 1997-2022 by Dimitri van Heesch.
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.
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
22 #include "classlist.h"
23 #include "cmdmapper.h"
27 #include "docparser.h"
28 #include "docparser_p.h"
33 #include "namespacedef.h"
37 #include "printdocvisitor.h"
39 #include "indexlist.h"
42 #define DBG(x) do {} while(0)
45 //#define DBG(x) printf x
48 //#define myprintf(...) fprintf(stderr,__VA_ARGS__)
49 //#define DBG(x) myprintf x
52 //---------------------------------------------------------------------------
54 IDocParserPtr createDocParser()
56 return std::make_unique<DocParser>();
59 //---------------------------------------------------------------------------
60 DocParser::~DocParser()
64 searchData.transfer();
68 err("Unexpected exception caught in DocParser\n");
72 void DocParser::pushContext()
75 //indent.fill(' ',contextStack.size()*2+2);
76 //printf("%sdocParserPushContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
78 tokenizer.pushContext();
79 contextStack.push(DocParserContext());
80 auto &ctx = contextStack.top();
82 ctx.lineNo = tokenizer.getLineNr();
83 context.token = tokenizer.newToken();
86 void DocParser::popContext()
88 auto &ctx = contextStack.top();
90 tokenizer.setLineNr(ctx.lineNo);
91 context.token = ctx.token;
92 tokenizer.replaceToken(context.token);
94 tokenizer.popContext();
97 //indent.fill(' ',contextStack.size()*2+2);
98 //printf("%sdocParserPopContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
101 //---------------------------------------------------------------------------
103 /*! search for an image in the imageNameDict and if found
104 * copies the image to the output directory (which depends on the \a type
107 QCString DocParser::findAndCopyImage(const QCString &fileName, DocImage::Type type, bool doWarn)
111 FileDef *fd = findFileDef(Doxygen::imageNameLinkedMap,fileName,ambig);
112 //printf("Search for %s\n",fileName);
118 text.sprintf("image file name %s is ambiguous.\n",qPrint(fileName));
119 text+="Possible candidates:\n";
120 text+=showFileDefMatches(Doxygen::imageNameLinkedMap,fileName);
121 warn_doc_error(context.fileName,tokenizer.getLineNr(),"%s", qPrint(text));
124 QCString inputFile = fd->absFilePath();
125 FileInfo infi(inputFile.str());
130 if ((i=result.findRev('/'))!=-1 || (i=result.findRev('\\'))!=-1)
132 result = result.right(static_cast<int>(result.length())-i-1);
134 //printf("fileName=%s result=%s\n",fileName,qPrint(result));
139 if (!Config_getBool(GENERATE_HTML)) return result;
140 outputDir = Config_getString(HTML_OUTPUT);
142 case DocImage::Latex:
143 if (!Config_getBool(GENERATE_LATEX)) return result;
144 outputDir = Config_getString(LATEX_OUTPUT);
146 case DocImage::DocBook:
147 if (!Config_getBool(GENERATE_DOCBOOK)) return result;
148 outputDir = Config_getString(DOCBOOK_OUTPUT);
151 if (!Config_getBool(GENERATE_RTF)) return result;
152 outputDir = Config_getString(RTF_OUTPUT);
155 if (!Config_getBool(GENERATE_XML)) return result;
156 outputDir = Config_getString(XML_OUTPUT);
159 QCString outputFile = outputDir+"/"+result;
160 FileInfo outfi(outputFile.str());
161 if (outfi.isSymLink())
163 Dir().remove(outputFile.str());
164 warn_doc_error(context.fileName,tokenizer.getLineNr(),
165 "destination of image %s is a symlink, replacing with image",
168 if (outputFile!=inputFile) // prevent copying to ourself
170 if (copyFile(inputFile,outputFile) && type==DocImage::Html)
172 Doxygen::indexList->addImageFile(result);
178 warn_doc_error(context.fileName,tokenizer.getLineNr(),
179 "could not open image %s",qPrint(fileName));
182 if (type==DocImage::Latex && Config_getBool(USE_PDFLATEX) &&
183 fd->name().endsWith(".eps")
185 { // we have an .eps image in pdflatex mode => convert it to a pdf.
186 QCString outputDir = Config_getString(LATEX_OUTPUT);
187 QCString baseName = fd->name().left(fd->name().length()-4);
188 QCString epstopdfArgs(4096);
189 epstopdfArgs.sprintf("\"%s/%s.eps\" --outfile=\"%s/%s.pdf\"",
190 qPrint(outputDir), qPrint(baseName),
191 qPrint(outputDir), qPrint(baseName));
192 Portable::sysTimerStart();
193 if (Portable::system("epstopdf",epstopdfArgs)!=0)
195 err("Problems running epstopdf. Check your TeX installation!\n");
197 Portable::sysTimerStop();
204 if (!result.startsWith("http:") && !result.startsWith("https:") && doWarn)
206 warn_doc_error(context.fileName,tokenizer.getLineNr(),
207 "image file %s is not found in IMAGE_PATH: "
208 "assuming external image.",qPrint(fileName)
215 /*! Collects the parameters found with \@param command
216 * in a list context.paramsFound. If
217 * the parameter is not an actual parameter of the current
218 * member context.memberDef, then a warning is raised (unless warnings
219 * are disabled altogether).
221 void DocParser::checkArgumentName()
223 if (!(Config_getBool(WARN_IF_DOC_ERROR) || Config_getBool(WARN_IF_INCOMPLETE_DOC))) return;
224 if (context.memberDef==0) return; // not a member
225 std::string name = context.token->name.str();
226 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
227 context.memberDef->argumentList() :
228 context.memberDef->declArgumentList();
229 SrcLangExt lang = context.memberDef->getLanguage();
230 //printf("isDocsForDefinition()=%d\n",context.memberDef->isDocsForDefinition());
231 if (al.empty()) return; // no argument list
233 static const reg::Ex re(R"(\$?\w+\.*)");
234 reg::Iterator it(name,re);
236 for (; it!=end ; ++it)
238 const auto &match = *it;
239 QCString aName=match.str();
240 if (lang==SrcLangExt_Fortran) aName=aName.lower();
241 //printf("aName='%s'\n",qPrint(aName));
243 for (const Argument &a : al)
245 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
246 if (lang==SrcLangExt_Fortran) argName=argName.lower();
247 argName=argName.stripWhiteSpace();
248 //printf("argName='%s' aName=%s\n",qPrint(argName),qPrint(aName));
249 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
252 context.paramsFound.insert(aName.str());
259 //printf("member type=%d\n",context.memberDef->memberType());
260 QCString scope=context.memberDef->getScopeString();
261 if (!scope.isEmpty()) scope+="::"; else scope="";
262 QCString inheritedFrom = "";
263 QCString docFile = context.memberDef->docFile();
264 int docLine = context.memberDef->docLine();
265 const MemberDef *inheritedMd = context.memberDef->inheritsDocsFrom();
266 if (inheritedMd) // documentation was inherited
268 inheritedFrom.sprintf(" inherited from member %s at line "
269 "%d in file %s",qPrint(inheritedMd->name()),
270 inheritedMd->docLine(),qPrint(inheritedMd->docFile()));
271 docFile = context.memberDef->getDefFileName();
272 docLine = context.memberDef->getDefLine();
274 QCString alStr = argListToString(al);
275 warn_doc_error(docFile,docLine,
276 "argument '%s' of command @param "
277 "is not found in the argument list of %s%s%s%s",
278 qPrint(aName), qPrint(scope), qPrint(context.memberDef->name()),
279 qPrint(alStr), qPrint(inheritedFrom));
283 /*! Collects the return values found with \@retval command
284 * in a global list g_parserContext.retvalsFound.
286 void DocParser::checkRetvalName()
288 QCString name = context.token->name;
289 if (!Config_getBool(WARN_IF_DOC_ERROR)) return;
290 if (context.memberDef==0 || name.isEmpty()) return; // not a member or no valid name
291 if (context.retvalsFound.count(name.str())==1) // only report the first double entry
293 warn_doc_error(context.memberDef->getDefFileName(),
294 context.memberDef->getDefLine(),
296 qPrint("return value '" + name + "' of " +
297 QCString(context.memberDef->qualifiedName()) +
298 " has multiple documentation sections"));
300 context.retvalsFound.insert(name.str());
303 /*! Checks if the parameters that have been specified using \@param are
304 * indeed all parameters and that a parameter does not have multiple
306 * Must be called after checkArgumentName() has been called for each
309 void DocParser::checkUnOrMultipleDocumentedParams()
311 if (context.memberDef && context.hasParamCommand)
313 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
314 context.memberDef->argumentList() :
315 context.memberDef->declArgumentList();
316 SrcLangExt lang = context.memberDef->getLanguage();
319 ArgumentList undocParams;
320 for (const Argument &a: al)
322 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
323 if (lang==SrcLangExt_Fortran) argName = argName.lower();
324 argName=argName.stripWhiteSpace();
325 QCString aName = argName;
326 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
327 if (lang==SrcLangExt_Python && (argName=="self" || argName=="cls"))
329 // allow undocumented self / cls parameter for Python
331 else if (!argName.isEmpty())
333 size_t count = context.paramsFound.count(argName.str());
334 if (count==0 && a.docs.isEmpty())
336 undocParams.push_back(a);
338 else if (count>1 && Config_getBool(WARN_IF_DOC_ERROR))
340 warn_doc_error(context.memberDef->docFile(),
341 context.memberDef->docLine(),
343 qPrint("argument '" + aName +
344 "' from the argument list of " +
345 QCString(context.memberDef->qualifiedName()) +
346 " has multiple @param documentation sections"));
350 if (!undocParams.empty() && Config_getBool(WARN_IF_INCOMPLETE_DOC))
353 QCString errMsg = "The following parameter";
354 if (undocParams.size()>1) errMsg+="s";
356 QCString(context.memberDef->qualifiedName()) +
357 QCString(argListToString(al)) +
358 (undocParams.size()>1 ? " are" : " is") + " not documented:\n";
359 for (const Argument &a : undocParams)
361 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
362 if (lang==SrcLangExt_Fortran) argName = argName.lower();
363 argName=argName.stripWhiteSpace();
364 if (!first) errMsg+="\n";
366 errMsg+=" parameter '"+argName+"'";
368 warn_incomplete_doc(context.memberDef->docFile(),
369 context.memberDef->docLine(),
371 qPrint(substitute(errMsg,"%","%%")));
376 if (!context.paramsFound.size() && Config_getBool(WARN_IF_DOC_ERROR))
378 warn_doc_error(context.memberDef->docFile(),
379 context.memberDef->docLine(),
381 qPrint(context.memberDef->qualifiedName() +
382 " has @param documentation sections but no arguments"));
389 //---------------------------------------------------------------------------
391 //---------------------------------------------------------------------------
393 //---------------------------------------------------------------------------
394 /*! Looks for a documentation block with name commandName in the current
395 * context (g_parserContext.context). The resulting documentation string is
396 * put in pDoc, the definition in which the documentation was found is
398 * @retval TRUE if name was found.
399 * @retval FALSE if name was not found.
401 bool DocParser::findDocsForMemberOrCompound(const QCString &commandName,
404 const Definition **pDef)
406 //printf("findDocsForMemberOrCompound(%s)\n",commandName);
410 QCString cmdArg=commandName;
411 if (cmdArg.isEmpty()) return FALSE;
414 const GroupDef *gd=0;
416 gd = Doxygen::groupLinkedMap->find(cmdArg);
419 *pDoc=gd->documentation();
420 *pBrief=gd->briefDescription();
424 pd = Doxygen::pageLinkedMap->find(cmdArg);
427 *pDoc=pd->documentation();
428 *pBrief=pd->briefDescription();
433 fd = findFileDef(Doxygen::inputNameLinkedMap,cmdArg,ambig);
434 if (fd && !ambig) // file
436 *pDoc=fd->documentation();
437 *pBrief=fd->briefDescription();
442 // for symbols we need to normalize the separator, so A#B, or A\B, or A.B becomes A::B
443 cmdArg = substitute(cmdArg,"#","::");
444 cmdArg = substitute(cmdArg,"\\","::");
445 bool extractAnonNs = Config_getBool(EXTRACT_ANON_NSPACES);
447 cmdArg.startsWith("anonymous_namespace{")
450 size_t rightBracePos = cmdArg.find("}", static_cast<int>(qstrlen("anonymous_namespace{")));
451 QCString leftPart = cmdArg.left(rightBracePos + 1);
452 QCString rightPart = cmdArg.right(cmdArg.size() - rightBracePos - 1);
453 rightPart = substitute(rightPart, ".", "::");
454 cmdArg = leftPart + rightPart;
458 cmdArg = substitute(cmdArg,".","::");
461 int l=static_cast<int>(cmdArg.length());
463 int funcStart=cmdArg.find('(');
470 // Check for the case of operator() and the like.
471 // beware of scenarios like operator()((foo)bar)
472 int secondParen = cmdArg.find('(', funcStart+1);
473 int leftParen = cmdArg.find(')', funcStart+1);
474 if (leftParen!=-1 && secondParen!=-1)
476 if (leftParen<secondParen)
478 funcStart=secondParen;
483 QCString name=removeRedundantWhiteSpace(cmdArg.left(funcStart));
484 QCString args=cmdArg.right(l-funcStart);
485 // try if the link is to a member
486 const MemberDef *md=0;
487 const ClassDef *cd=0;
488 const NamespaceDef *nd=0;
489 bool found = getDefs(
490 context.context.find('.')==-1?context.context:QCString(), // find('.') is a hack to detect files
492 args.isEmpty() ? QCString() : args,
493 md,cd,fd,nd,gd,FALSE,0,TRUE);
494 //printf("found=%d context=%s name=%s\n",found,qPrint(context.context),qPrint(name));
497 *pDoc=md->documentation();
498 *pBrief=md->briefDescription();
504 int scopeOffset=static_cast<int>(context.context.length());
507 QCString fullName=cmdArg;
510 fullName.prepend(context.context.left(scopeOffset)+"::");
512 //printf("Trying fullName='%s'\n",qPrint(fullName));
514 // try class, namespace, group, page, file reference
515 cd = Doxygen::classLinkedMap->find(fullName);
518 *pDoc=cd->documentation();
519 *pBrief=cd->briefDescription();
523 nd = Doxygen::namespaceLinkedMap->find(fullName);
526 *pDoc=nd->documentation();
527 *pBrief=nd->briefDescription();
537 scopeOffset = context.context.findRev("::",scopeOffset-1);
538 if (scopeOffset==-1) scopeOffset=0;
540 } while (scopeOffset>=0);
546 //---------------------------------------------------------------------------
547 void DocParser::errorHandleDefaultToken(DocNodeVariant *parent,int tok,
548 DocNodeList &children,const QCString &txt)
550 const char *cmd_start = "\\";
557 children.append<DocWord>(this,parent,TK_COMMAND_CHAR(tok) + context.token->name);
558 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Illegal command %s found as part of a %s",
559 qPrint(cmd_start + context.token->name),qPrint(txt));
562 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported symbol %s found as part of a %s",
563 qPrint(context.token->name), qPrint(txt));
566 children.append<DocWord>(this,parent,context.token->name);
567 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unexpected token %s found as part of a %s",
568 DocTokenizer::tokToString(tok), qPrint(txt));
573 //---------------------------------------------------------------------------
575 int DocParser::handleStyleArgument(DocNodeVariant *parent,DocNodeList &children,const QCString &cmdName)
577 DBG(("handleStyleArgument(%s)\n",qPrint(cmdName)));
578 QCString saveCmdName = cmdName;
579 int tok=tokenizer.lex();
580 if (tok!=TK_WHITESPACE)
582 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
583 qPrint(saveCmdName));
586 while ((tok=tokenizer.lex()) &&
587 tok!=TK_WHITESPACE &&
593 static const reg::Ex specialChar(R"([.,|()\[\]:;?])");
594 if (tok==TK_WORD && context.token->name.length()==1 &&
595 reg::match(context.token->name.str(),specialChar))
597 // special character that ends the markup command
600 if (!defaultHandleToken(parent,tok,children))
605 if (insideLI(parent) && Mappers::htmlTagMapper->map(context.token->name) && context.token->endTag)
606 { // ignore </li> as the end of a style command
609 DBG(("handleStyleArgument(%s) end tok=%s\n",qPrint(saveCmdName), DocTokenizer::tokToString(tok)));
613 errorHandleDefaultToken(parent,tok,children,"\\" + saveCmdName + " command");
619 DBG(("handleStyleArgument(%s) end tok=%s\n",qPrint(saveCmdName), DocTokenizer::tokToString(tok)));
620 return (tok==TK_NEWPARA || tok==TK_LISTITEM || tok==TK_ENDLIST
624 /*! Called when a style change starts. For instance a \<b\> command is
627 void DocParser::handleStyleEnter(DocNodeVariant *parent,DocNodeList &children,
628 DocStyleChange::Style s,const QCString &tagName,const HtmlAttribList *attribs)
630 DBG(("HandleStyleEnter\n"));
631 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),s,tagName,TRUE,attribs);
632 context.styleStack.push(&children.back());
635 /*! Called when a style change ends. For instance a \</b\> command is
638 void DocParser::handleStyleLeave(DocNodeVariant *parent,DocNodeList &children,
639 DocStyleChange::Style s,const QCString &tagName)
641 DBG(("HandleStyleLeave\n"));
642 QCString tagNameLower = QCString(tagName).lower();
644 auto topStyleChange = [](const DocStyleChangeStack &stack) -> const DocStyleChange &
646 return std::get<DocStyleChange>(*stack.top());
649 if (context.styleStack.empty() || // no style change
650 topStyleChange(context.styleStack).style()!=s || // wrong style change
651 topStyleChange(context.styleStack).tagName()!=tagNameLower || // wrong style change
652 topStyleChange(context.styleStack).position()!=context.nodeStack.size() // wrong position
655 if (context.styleStack.empty())
657 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag without matching <%s>",
658 qPrint(tagName),qPrint(tagName));
660 else if (topStyleChange(context.styleStack).tagName()!=tagNameLower)
662 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
663 qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
665 else if (topStyleChange(context.styleStack).style()!=s)
667 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
668 qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
672 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> at different nesting level (%zu) than expected (%zu)",
673 qPrint(tagName),context.nodeStack.size(),topStyleChange(context.styleStack).position());
676 else // end the section
678 children.append<DocStyleChange>(
679 this,parent,context.nodeStack.size(),s,
680 topStyleChange(context.styleStack).tagName(),FALSE);
681 context.styleStack.pop();
685 /*! Called at the end of a paragraph to close all open style changes
686 * (e.g. a <b> without a </b>). The closed styles are pushed onto a stack
687 * and entered again at the start of a new paragraph.
689 void DocParser::handlePendingStyleCommands(DocNodeVariant *parent,DocNodeList &children)
691 if (!context.styleStack.empty())
693 const DocStyleChange *sc = &std::get<DocStyleChange>(*context.styleStack.top());
694 while (sc && sc->position()>=context.nodeStack.size())
695 { // there are unclosed style modifiers in the paragraph
696 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),
697 sc->style(),sc->tagName(),FALSE);
698 context.initialStyleStack.push(context.styleStack.top());
699 context.styleStack.pop();
700 sc = !context.styleStack.empty() ? &std::get<DocStyleChange>(*context.styleStack.top()) : 0;
705 void DocParser::handleInitialStyleCommands(DocNodeVariant *parent,DocNodeList &children)
707 while (!context.initialStyleStack.empty())
709 const DocStyleChange &sc = std::get<DocStyleChange>(*context.initialStyleStack.top());
710 handleStyleEnter(parent,children,sc.style(),sc.tagName(),&sc.attribs());
711 context.initialStyleStack.pop();
715 int DocParser::handleAHref(DocNodeVariant *parent,DocNodeList &children,
716 const HtmlAttribList &tagHtmlAttribs)
719 int retval = RetVal_OK;
720 for (const auto &opt : tagHtmlAttribs)
722 if (opt.name=="name" || opt.name=="id") // <a name=label> or <a id=label> tag
724 if (!opt.value.isEmpty())
726 children.append<DocAnchor>(this,parent,opt.value,TRUE);
727 break; // stop looking for other tag attribs
731 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <a> tag with name option but without value!");
734 else if (opt.name=="href") // <a href=url>..</a> tag
737 HtmlAttribList attrList = tagHtmlAttribs;
738 // and remove the href attribute
739 attrList.erase(attrList.begin()+index);
741 if (opt.value.at(0) != '#') relPath = context.relPath;
742 auto vDocHRef = children.append<DocHRef>(this, parent, attrList,
744 convertNameToFile(context.fileName, FALSE, TRUE));
745 DocHRef *href = children.get_last<DocHRef>();
746 context.insideHtmlLink=TRUE;
747 retval = href->parse(vDocHRef);
748 context.insideHtmlLink=FALSE;
751 else // unsupported option for tag a
759 void DocParser::handleUnclosedStyleCommands()
761 if (!context.initialStyleStack.empty())
763 QCString tagName = std::get<DocStyleChange>(*context.initialStyleStack.top()).tagName();
764 context.initialStyleStack.pop();
765 handleUnclosedStyleCommands();
766 warn_doc_error(context.fileName,tokenizer.getLineNr(),
767 "end of comment block while expecting "
768 "command </%s>",qPrint(tagName));
772 void DocParser::handleLinkedWord(DocNodeVariant *parent,DocNodeList &children,bool ignoreAutoLinkFlag)
774 QCString name = linkToText(SrcLangExt_Unknown,context.token->name,TRUE);
775 bool autolinkSupport = Config_getBool(AUTOLINK_SUPPORT);
776 if (!autolinkSupport && !ignoreAutoLinkFlag) // no autolinking -> add as normal word
778 children.append<DocWord>(this,parent,name);
782 // ------- try to turn the word 'name' into a link
784 const Definition *compound=0;
785 const MemberDef *member=0;
786 uint len = context.token->name.length();
789 FileDef *fd = findFileDef(Doxygen::inputNameLinkedMap,context.fileName,ambig);
790 //printf("handleLinkedWord(%s) context.context=%s\n",qPrint(context.token->name),qPrint(context.context));
791 if (!context.insideHtmlLink &&
792 (resolveRef(context.context,context.token->name,context.inSeeBlock,&compound,&member,TRUE,fd,TRUE)
793 || (!context.context.isEmpty() && // also try with global scope
794 resolveRef("",context.token->name,context.inSeeBlock,&compound,&member,FALSE,0,TRUE))
798 //printf("resolveRef %s = %p (linkable?=%d)\n",qPrint(context.token->name),member,member ? member->isLinkable() : FALSE);
799 if (member && member->isLinkable()) // member link
801 if (member->isObjCMethod())
803 bool localLink = context.memberDef ? member->getClassDef()==context.memberDef->getClassDef() : FALSE;
804 name = member->objCMethodName(localLink,context.inSeeBlock);
806 children.append<DocLinkedWord>(
808 member->getReference(),
809 member->getOutputFileBase(),
811 member->briefDescriptionAsTooltip());
813 else if (compound->isLinkable()) // compound link
815 QCString anchor = compound->anchor();
816 if (compound->definitionType()==Definition::TypeFile)
818 name=context.token->name;
820 else if (compound->definitionType()==Definition::TypeGroup)
822 name=toGroupDef(compound)->groupTitle();
824 children.append<DocLinkedWord>(
826 compound->getReference(),
827 compound->getOutputFileBase(),
829 compound->briefDescriptionAsTooltip());
831 else if (compound->definitionType()==Definition::TypeFile &&
832 (toFileDef(compound))->generateSourceFile()
833 ) // undocumented file that has source code we can link to
835 children.append<DocLinkedWord>(
836 this,parent,context.token->name,
837 compound->getReference(),
838 compound->getSourceFileBase(),
840 compound->briefDescriptionAsTooltip());
844 children.append<DocWord>(this,parent,name);
847 else if (!context.insideHtmlLink && len>1 && context.token->name.at(len-1)==':')
849 // special case, where matching Foo: fails to be an Obj-C reference,
850 // but Foo itself might be linkable.
851 context.token->name=context.token->name.left(len-1);
852 handleLinkedWord(parent,children,ignoreAutoLinkFlag);
853 children.append<DocWord>(this,parent,":");
855 else if (!context.insideHtmlLink && (cd=getClass(context.token->name+"-p")))
857 // special case 2, where the token name is not a class, but could
858 // be a Obj-C protocol
859 children.append<DocLinkedWord>(
862 cd->getOutputFileBase(),
864 cd->briefDescriptionAsTooltip());
866 else // normal non-linkable word
868 if (context.token->name.startsWith("#") || context.token->name.startsWith("::"))
870 warn_doc_error(context.fileName,tokenizer.getLineNr(),"explicit link request to '%s' could not be resolved",qPrint(name));
871 children.append<DocWord>(this,parent,context.token->name);
875 children.append<DocWord>(this,parent,name);
880 void DocParser::handleParameterType(DocNodeVariant *parent,DocNodeList &children,const QCString ¶mTypes)
882 QCString name = context.token->name; // save token name
885 while ((i=paramTypes.find('|',p))!=-1)
887 name1 = paramTypes.mid(p,i-p);
889 context.token->name=ii!=-1 ? name1.mid(0,ii) : name1; // take part without []
890 handleLinkedWord(parent,children);
891 if (ii!=-1) children.append<DocWord>(this,parent,name1.mid(ii)); // add [] part
893 children.append<DocSeparator>(this,parent,"|");
895 name1 = paramTypes.mid(p);
897 context.token->name=ii!=-1 ? name1.mid(0,ii) : name1;
898 handleLinkedWord(parent,children);
899 if (ii!=-1) children.append<DocWord>(this,parent,name1.mid(ii));
900 context.token->name = name; // restore original token name
903 void DocParser::handleInternalRef(DocNodeVariant *parent,DocNodeList &children)
905 //printf("CMD_INTERNALREF\n");
906 int tok=tokenizer.lex();
907 QCString tokenName = context.token->name;
908 if (tok!=TK_WHITESPACE)
910 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
914 tokenizer.setStateInternalRef();
915 tok=tokenizer.lex(); // get the reference id
916 if (tok!=TK_WORD && tok!=TK_LNKWORD)
918 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
919 DocTokenizer::tokToString(tok),qPrint(tokenName));
922 auto vDocInternalRef = children.append<DocInternalRef>(this,parent,context.token->name);
923 children.get_last<DocInternalRef>()->parse(vDocInternalRef);
926 void DocParser::handleAnchor(DocNodeVariant *parent,DocNodeList &children)
928 int tok=tokenizer.lex();
929 if (tok!=TK_WHITESPACE)
931 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
932 qPrint(context.token->name));
935 tokenizer.setStateAnchor();
939 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected end of comment block while parsing the "
940 "argument of command %s",qPrint(context.token->name));
943 else if (tok!=TK_WORD && tok!=TK_LNKWORD)
945 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
946 DocTokenizer::tokToString(tok),qPrint(context.token->name));
949 tokenizer.setStatePara();
950 children.append<DocAnchor>(this,parent,context.token->name,FALSE);
954 /* Helper function that deals with the title, width, and height arguments of various commands.
955 * @param[in] cmd Command id for which to extract caption and size info.
956 * @param[in] parent Parent node, owner of the children list passed as
957 * the third argument.
958 * @param[in] children The list of child nodes to which the node representing
959 * the token can be added.
960 * @param[out] width the extracted width specifier
961 * @param[out] height the extracted height specifier
963 void DocParser::defaultHandleTitleAndSize(const int cmd, DocNodeVariant *parent, DocNodeList &children, QCString &width,QCString &height)
965 auto ns = AutoNodeStack(this,parent);
968 tokenizer.setStateTitle();
970 while ((tok=tokenizer.lex()))
972 if (tok==TK_WORD && (context.token->name=="width=" || context.token->name=="height="))
974 // special case: no title, but we do have a size indicator
977 else if (tok==TK_HTMLTAG)
979 tokenizer.unputString(context.token->name);
982 if (!defaultHandleToken(parent,tok,children))
984 errorHandleDefaultToken(parent,tok,children,Mappers::cmdMapper->find(cmd));
987 // parse size attributes
992 while (tok==TK_WHITESPACE || tok==TK_WORD) // there are values following the title
996 if (context.token->name=="width=" || context.token->name=="height=")
998 tokenizer.setStateTitleAttrValue();
999 context.token->name = context.token->name.left(context.token->name.length()-1);
1002 if (context.token->name=="width")
1004 width = context.token->chars;
1006 else if (context.token->name=="height")
1008 height = context.token->chars;
1012 tokenizer.unputString(context.token->name);
1013 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unknown option '%s' after \\%s command, expected 'width' or 'height'",
1014 qPrint(context.token->name), qPrint(Mappers::cmdMapper->find(cmd)));
1019 tok=tokenizer.lex();
1020 // if we found something we did not expect, push it back to the stream
1021 // so it can still be processed
1022 if (tok==TK_COMMAND_AT || tok==TK_COMMAND_BS)
1024 tokenizer.unputString(context.token->name);
1025 tokenizer.unputString(tok==TK_COMMAND_AT ? "@" : "\\");
1027 else if (tok==TK_SYMBOL || tok==TK_HTMLTAG)
1029 tokenizer.unputString(context.token->name);
1032 tokenizer.setStatePara();
1034 handlePendingStyleCommands(parent,children);
1037 void DocParser::handleImage(DocNodeVariant *parent, DocNodeList &children)
1039 bool inlineImage = false;
1042 int tok=tokenizer.lex();
1043 if (tok!=TK_WHITESPACE)
1047 if (context.token->name == "{")
1049 tokenizer.setStateOptions();
1050 tok=tokenizer.lex();
1051 tokenizer.setStatePara();
1052 StringVector optList=split(context.token->name.str(),",");
1053 for (const auto &opt : optList)
1055 if (opt.empty()) continue;
1056 QCString locOpt(opt);
1058 locOpt = locOpt.stripWhiteSpace();
1059 locOptLow = locOpt.lower();
1060 if (locOptLow == "inline")
1064 else if (locOptLow.startsWith("anchor:"))
1066 if (!anchorStr.isEmpty())
1068 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1069 "multiple use of option 'anchor' for 'image' command, ignoring: '%s'",
1070 qPrint(locOpt.mid(7)));
1074 anchorStr = locOpt.mid(7);
1079 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1080 "unknown option '%s' for 'image' command specified",
1084 tok=tokenizer.lex();
1085 if (tok!=TK_WHITESPACE)
1087 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1094 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1098 tok=tokenizer.lex();
1099 if (tok!=TK_WORD && tok!=TK_LNKWORD)
1101 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1102 DocTokenizer::tokToString(tok));
1105 tok=tokenizer.lex();
1106 if (tok!=TK_WHITESPACE)
1108 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1112 QCString imgType = context.token->name.lower();
1113 if (imgType=="html") t=DocImage::Html;
1114 else if (imgType=="latex") t=DocImage::Latex;
1115 else if (imgType=="docbook") t=DocImage::DocBook;
1116 else if (imgType=="rtf") t=DocImage::Rtf;
1117 else if (imgType=="xml") t=DocImage::Xml;
1120 warn_doc_error(context.fileName,tokenizer.getLineNr(),"output format `%s` specified as the first argument of "
1121 "\\image command is not valid",
1125 tokenizer.setStateFile();
1126 tok=tokenizer.lex();
1127 tokenizer.setStatePara();
1130 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1131 DocTokenizer::tokToString(tok));
1134 if (!anchorStr.isEmpty())
1136 children.append<DocAnchor>(this,parent,anchorStr,true);
1138 HtmlAttribList attrList;
1139 auto vDocImage = children.append<DocImage>(this,parent,attrList,
1140 findAndCopyImage(context.token->name,t),t,"",inlineImage);
1141 children.get_last<DocImage>()->parse(vDocImage);
1145 /* Helper function that deals with the most common tokens allowed in
1146 * title like sections.
1147 * @param parent Parent node, owner of the children list passed as
1148 * the third argument.
1149 * @param tok The token to process.
1150 * @param children The list of child nodes to which the node representing
1151 * the token can be added.
1152 * @param handleWord Indicates if word token should be processed
1153 * @retval TRUE The token was handled.
1154 * @retval FALSE The token was not handled.
1156 bool DocParser::defaultHandleToken(DocNodeVariant *parent,int tok, DocNodeList &children,bool handleWord)
1158 DBG(("token %s at %d",DocTokenizer::tokToString(tok),tokenizer.getLineNr()));
1159 if (tok==TK_WORD || tok==TK_LNKWORD || tok==TK_SYMBOL || tok==TK_URL ||
1160 tok==TK_COMMAND_AT || tok==TK_COMMAND_BS || tok==TK_HTMLTAG
1163 DBG((" name=%s",qPrint(context.token->name)));
1167 QCString tokenName = context.token->name;
1173 switch (Mappers::cmdMapper->map(tokenName))
1176 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_BSlash);
1179 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_At);
1182 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Less);
1185 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Greater);
1188 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Amp);
1191 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dollar);
1194 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Hash);
1197 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_DoubleColon);
1200 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Percent);
1203 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1204 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1207 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1208 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1209 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1212 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Quot);
1215 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dot);
1218 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Plus);
1221 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1224 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Equal);
1228 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Italic,tokenName,TRUE);
1229 tok=handleStyleArgument(parent,children,tokenName);
1230 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Italic,tokenName,FALSE);
1231 if (tok!=TK_WORD) children.append<DocWhiteSpace>(this,parent," ");
1232 if (tok==TK_NEWPARA) goto handlepara;
1233 else if (tok==TK_WORD || tok==TK_HTMLTAG)
1235 DBG(("CMD_EMPHASIS: reparsing command %s\n",qPrint(context.token->name)));
1242 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Bold,tokenName,TRUE);
1243 tok=handleStyleArgument(parent,children,tokenName);
1244 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Bold,tokenName,FALSE);
1245 if (tok!=TK_WORD) children.append<DocWhiteSpace>(this,parent," ");
1246 if (tok==TK_NEWPARA) goto handlepara;
1247 else if (tok==TK_WORD || tok==TK_HTMLTAG)
1249 DBG(("CMD_BOLD: reparsing command %s\n",qPrint(context.token->name)));
1256 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Code,tokenName,TRUE);
1257 tok=handleStyleArgument(parent,children,tokenName);
1258 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Code,tokenName,FALSE);
1259 if (tok!=TK_WORD) children.append<DocWhiteSpace>(this,parent," ");
1260 if (tok==TK_NEWPARA) goto handlepara;
1261 else if (tok==TK_WORD || tok==TK_HTMLTAG)
1263 DBG(("CMD_CODE: reparsing command %s\n",qPrint(context.token->name)));
1270 tokenizer.setStateHtmlOnly();
1271 tok = tokenizer.lex();
1272 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::HtmlOnly,context.isExample,context.exampleName,context.token->name=="block");
1273 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"htmlonly section ended without end marker");
1274 tokenizer.setStatePara();
1279 tokenizer.setStateManOnly();
1280 tok = tokenizer.lex();
1281 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::ManOnly,context.isExample,context.exampleName);
1282 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"manonly section ended without end marker");
1283 tokenizer.setStatePara();
1288 tokenizer.setStateRtfOnly();
1289 tok = tokenizer.lex();
1290 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::RtfOnly,context.isExample,context.exampleName);
1291 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"rtfonly section ended without end marker");
1292 tokenizer.setStatePara();
1297 tokenizer.setStateLatexOnly();
1298 tok = tokenizer.lex();
1299 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::LatexOnly,context.isExample,context.exampleName);
1300 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"latexonly section ended without end marker");
1301 tokenizer.setStatePara();
1306 tokenizer.setStateXmlOnly();
1307 tok = tokenizer.lex();
1308 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::XmlOnly,context.isExample,context.exampleName);
1309 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"xmlonly section ended without end marker");
1310 tokenizer.setStatePara();
1315 tokenizer.setStateDbOnly();
1316 tok = tokenizer.lex();
1317 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::DocbookOnly,context.isExample,context.exampleName);
1318 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"docbookonly section ended without end marker");
1319 tokenizer.setStatePara();
1324 children.append<DocFormula>(this,parent,context.token->id);
1329 handleAnchor(parent,children);
1332 case CMD_INTERNALREF:
1334 handleInternalRef(parent,children);
1335 tokenizer.setStatePara();
1341 tokenizer.setStateSetScope();
1342 (void)tokenizer.lex();
1343 scope = context.token->name;
1344 context.context = scope;
1345 //printf("Found scope='%s'\n",qPrint(scope));
1346 tokenizer.setStatePara();
1350 handleImage(parent,children);
1358 switch (Mappers::htmlTagMapper->map(tokenName))
1361 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <div> tag in heading\n");
1364 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <pre> tag in heading\n");
1367 if (!context.token->endTag)
1369 handleStyleEnter(parent,children,DocStyleChange::Bold,tokenName,&context.token->attribs);
1373 handleStyleLeave(parent,children,DocStyleChange::Bold,tokenName);
1377 if (!context.token->endTag)
1379 handleStyleEnter(parent,children,DocStyleChange::S,tokenName,&context.token->attribs);
1383 handleStyleLeave(parent,children,DocStyleChange::S,tokenName);
1387 if (!context.token->endTag)
1389 handleStyleEnter(parent,children,DocStyleChange::Strike,tokenName,&context.token->attribs);
1393 handleStyleLeave(parent,children,DocStyleChange::Strike,tokenName);
1397 if (!context.token->endTag)
1399 handleStyleEnter(parent,children,DocStyleChange::Del,tokenName,&context.token->attribs);
1403 handleStyleLeave(parent,children,DocStyleChange::Del,tokenName);
1406 case HTML_UNDERLINE:
1407 if (!context.token->endTag)
1409 handleStyleEnter(parent,children,DocStyleChange::Underline,tokenName,&context.token->attribs);
1413 handleStyleLeave(parent,children,DocStyleChange::Underline,tokenName);
1417 if (!context.token->endTag)
1419 handleStyleEnter(parent,children,DocStyleChange::Ins,tokenName,&context.token->attribs);
1423 handleStyleLeave(parent,children,DocStyleChange::Ins,tokenName);
1428 if (!context.token->endTag)
1430 handleStyleEnter(parent,children,DocStyleChange::Code,tokenName,&context.token->attribs);
1434 handleStyleLeave(parent,children,DocStyleChange::Code,tokenName);
1438 if (!context.token->endTag)
1440 handleStyleEnter(parent,children,DocStyleChange::Italic,tokenName,&context.token->attribs);
1444 handleStyleLeave(parent,children,DocStyleChange::Italic,tokenName);
1448 if (!context.token->endTag)
1450 handleStyleEnter(parent,children,DocStyleChange::Subscript,tokenName,&context.token->attribs);
1454 handleStyleLeave(parent,children,DocStyleChange::Subscript,tokenName);
1458 if (!context.token->endTag)
1460 handleStyleEnter(parent,children,DocStyleChange::Superscript,tokenName,&context.token->attribs);
1464 handleStyleLeave(parent,children,DocStyleChange::Superscript,tokenName);
1468 if (!context.token->endTag)
1470 handleStyleEnter(parent,children,DocStyleChange::Center,tokenName,&context.token->attribs);
1474 handleStyleLeave(parent,children,DocStyleChange::Center,tokenName);
1478 if (!context.token->endTag)
1480 handleStyleEnter(parent,children,DocStyleChange::Small,tokenName,&context.token->attribs);
1484 handleStyleLeave(parent,children,DocStyleChange::Small,tokenName);
1488 if (!context.token->endTag)
1490 handleStyleEnter(parent,children,DocStyleChange::Cite,tokenName,&context.token->attribs);
1494 handleStyleLeave(parent,children,DocStyleChange::Cite,tokenName);
1498 if (!context.token->endTag)
1499 handleImg(parent,children,context.token->attribs);
1509 HtmlEntityMapper::SymType s = DocSymbol::decodeSymbol(tokenName);
1510 if (s!=HtmlEntityMapper::Sym_Unknown)
1512 children.append<DocSymbol>(this,parent,s);
1523 if (insidePRE(parent) || !children.empty())
1525 children.append<DocWhiteSpace>(this,parent,context.token->chars);
1531 handleLinkedWord(parent,children);
1539 children.append<DocWord>(this,parent,context.token->name);
1545 if (context.insideHtmlLink)
1547 children.append<DocWord>(this,parent,context.token->name);
1551 children.append<DocURL>(this,parent,context.token->name,context.token->isEMailAddr);
1560 //---------------------------------------------------------------------------
1562 void DocParser::handleImg(DocNodeVariant *parent, DocNodeList &children,const HtmlAttribList &tagHtmlAttribs)
1566 for (const auto &opt : tagHtmlAttribs)
1568 //printf("option name=%s value=%s\n",qPrint(opt.name),qPrint(opt.value));
1569 if (opt.name=="src" && !opt.value.isEmpty())
1572 HtmlAttribList attrList = tagHtmlAttribs;
1573 // and remove the src attribute
1574 attrList.erase(attrList.begin()+index);
1575 DocImage::Type t = DocImage::Html;
1576 children.append<DocImage>(
1577 this,parent,attrList,
1578 findAndCopyImage(opt.value,t,false),
1586 warn_doc_error(context.fileName,tokenizer.getLineNr(),"IMG tag does not have a SRC attribute!\n");
1590 //---------------------------------------------------------------------------
1592 int DocParser::internalValidatingParseDoc(DocNodeVariant *parent,DocNodeList &children,
1593 const QCString &doc)
1595 int retval = RetVal_OK;
1597 if (doc.isEmpty()) return retval;
1599 tokenizer.init(doc.data(),context.fileName,context.markdownSupport);
1601 // first parse any number of paragraphs
1603 DocPara *lastPar=!children.empty() ? std::get_if<DocPara>(&children.back()): 0;
1605 { // last child item was a paragraph
1610 auto vDocPara = children.append<DocPara>(this,parent);
1611 DocPara *par = children.get_last<DocPara>();
1612 if (isFirst) { par->markFirst(); isFirst=FALSE; }
1613 retval=par->parse(vDocPara);
1614 if (!par->isEmpty())
1616 if (lastPar) lastPar->markLast(FALSE);
1621 children.pop_back();
1623 } while (retval==TK_NEWPARA);
1624 if (lastPar) lastPar->markLast();
1626 //printf("internalValidateParsingDoc: %p: isFirst=%d isLast=%d\n",
1627 // lastPar,lastPar?lastPar->isFirst():-1,lastPar?lastPar->isLast():-1);
1632 //---------------------------------------------------------------------------
1634 void DocParser::readTextFileByName(const QCString &file,QCString &text)
1636 if (Portable::isAbsolutePath(file))
1638 FileInfo fi(file.str());
1641 text = fileToString(file,Config_getBool(FILTER_SOURCE_FILES));
1645 const StringVector &examplePathList = Config_getList(EXAMPLE_PATH);
1646 for (const auto &s : examplePathList)
1648 std::string absFileName = s+(Portable::pathSeparator()+file).str();
1649 FileInfo fi(absFileName);
1652 text = fileToString(QCString(absFileName),Config_getBool(FILTER_SOURCE_FILES));
1657 // as a fallback we also look in the exampleNameDict
1659 FileDef *fd = findFileDef(Doxygen::exampleNameLinkedMap,file,ambig);
1662 text = fileToString(fd->absFilePath(),Config_getBool(FILTER_SOURCE_FILES));
1665 warn_doc_error(context.fileName,tokenizer.getLineNr(),"included file name %s is ambiguous"
1666 "Possible candidates:\n%s",qPrint(file),
1667 qPrint(showFileDefMatches(Doxygen::exampleNameLinkedMap,file))
1673 warn_doc_error(context.fileName,tokenizer.getLineNr(),"included file %s is not found. "
1674 "Check your EXAMPLE_PATH",qPrint(file));
1678 //---------------------------------------------------------------------------
1680 static QCString extractCopyDocId(const char *data, uint &j, uint len)
1684 bool insideDQuote=FALSE;
1685 bool insideSQuote=FALSE;
1687 while (j<len && !found)
1689 if (!insideSQuote && !insideDQuote)
1693 case '(': round++; break;
1694 case ')': round--; break;
1695 case '"': insideDQuote=TRUE; break;
1696 case '\'': insideSQuote=TRUE; break;
1697 case ' ': // fall through
1698 case '\t': // fall through
1704 else if (insideSQuote) // look for single quote end
1706 if (data[j]=='\'' && (j==0 || data[j]!='\\'))
1711 else if (insideDQuote) // look for double quote end
1713 if (data[j]=='"' && (j==0 || data[j]!='\\'))
1720 if (qstrncmp(data+j," const",6)==0)
1724 else if (qstrncmp(data+j," volatile",9)==0)
1729 if (j>0 && data[j-1]=='.') { e--; } // do not include punctuation added by Definition::_setBriefDescription()
1730 QCString id(data+s,e-s);
1731 //printf("extractCopyDocId='%s' input='%s'\n",qPrint(id),&data[s]);
1735 // macro to check if the input starts with a specific command.
1736 // note that data[i] should point to the start of the command (\ or @ character)
1737 // and the sizeof(str) returns the size of str including the '\0' terminator;
1738 // a fact we abuse to skip over the start of the command character.
1739 #define CHECK_FOR_COMMAND(str,action) \
1740 do if ((i+sizeof(str)<len) && qstrncmp(data+i+1,str,sizeof(str)-1)==0) \
1741 { j=i+sizeof(str); action; } while(0)
1743 static uint isCopyBriefOrDetailsCmd(const char *data, uint i,uint len,bool &brief)
1746 if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1748 CHECK_FOR_COMMAND("copybrief",brief=TRUE); // @copybrief or \copybrief
1749 CHECK_FOR_COMMAND("copydetails",brief=FALSE); // @copydetails or \copydetails
1754 static uint isVerbatimSection(const char *data,uint i,uint len,QCString &endMarker)
1757 if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1759 CHECK_FOR_COMMAND("dot",endMarker="enddot");
1760 CHECK_FOR_COMMAND("icode",endMarker="endicode");
1761 CHECK_FOR_COMMAND("code",endMarker="endcode");
1762 CHECK_FOR_COMMAND("msc",endMarker="endmsc");
1763 CHECK_FOR_COMMAND("iverbatim",endMarker="endiverbatim");
1764 CHECK_FOR_COMMAND("verbatim",endMarker="endverbatim");
1765 CHECK_FOR_COMMAND("iliteral",endMarker="endiliteral");
1766 CHECK_FOR_COMMAND("latexonly",endMarker="endlatexonly");
1767 CHECK_FOR_COMMAND("htmlonly",endMarker="endhtmlonly");
1768 CHECK_FOR_COMMAND("xmlonly",endMarker="endxmlonly");
1769 CHECK_FOR_COMMAND("rtfonly",endMarker="endrtfonly");
1770 CHECK_FOR_COMMAND("manonly",endMarker="endmanonly");
1771 CHECK_FOR_COMMAND("docbookonly",endMarker="enddocbookonly");
1772 CHECK_FOR_COMMAND("startuml",endMarker="enduml");
1774 //printf("isVerbatimSection(%s)=%d)\n",qPrint(QCString(&data[i]).left(10)),j);
1778 static uint skipToEndMarker(const char *data,uint i,uint len,const QCString &endMarker)
1782 if ((data[i]=='@' || data[i]=='\\') && // start of command character
1783 (i==0 || (data[i-1]!='@' && data[i-1]!='\\'))) // that is not escaped
1785 if (i+endMarker.length()+1<=len && qstrncmp(data+i+1,endMarker.data(),endMarker.length())==0)
1787 return i+endMarker.length()+1;
1792 // oops no endmarker found...
1793 return i<len ? i+1 : len;
1797 QCString DocParser::processCopyDoc(const char *data,uint &len)
1799 //printf("processCopyDoc start '%s'\n",data);
1802 int lineNr = tokenizer.getLineNr();
1806 if (c=='@' || c=='\\') // look for a command
1809 uint j=isCopyBriefOrDetailsCmd(data,i,len,isBrief);
1813 while (j<len && (data[j]==' ' || data[j]=='\t')) j++;
1814 // extract the argument
1815 QCString id = extractCopyDocId(data,j,len);
1816 const Definition *def = 0;
1818 //printf("resolving docs='%s'\n",qPrint(id));
1819 if (findDocsForMemberOrCompound(id,&doc,&brief,&def))
1821 //printf("found it def=%p brief='%s' doc='%s' isBrief=%d\n",def,qPrint(brief),qPrint(doc),isBrief);
1822 auto it = std::find(context.copyStack.begin(),context.copyStack.end(),def);
1823 if (it==context.copyStack.end()) // definition not parsed earlier
1825 QCString orgFileName = context.fileName;
1826 context.copyStack.push_back(def);
1829 buf.addStr("\\ifile \""+QCString(def->briefFile())+"\" ");
1830 buf.addStr("\\iline "+QCString().setNum(def->briefLine())+" ");
1831 uint l=static_cast<uint>(brief.length());
1832 buf.addStr(processCopyDoc(brief.data(),l));
1836 buf.addStr("\\ifile \""+QCString(def->docFile())+"\" ");
1837 buf.addStr("\\iline "+QCString().setNum(def->docLine())+" ");
1838 uint l=static_cast<uint>(doc.length());
1839 buf.addStr(processCopyDoc(doc.data(),l));
1841 context.copyStack.pop_back();
1842 buf.addStr("\\ifile \""+context.fileName+"\" ");
1843 buf.addStr("\\iline "+QCString().setNum(lineNr)+" ");
1847 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1848 "Found recursive @copy%s or @copydoc relation for argument '%s'.\n",
1849 isBrief?"brief":"details",qPrint(id));
1854 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1855 "@copy%s or @copydoc target '%s' not found", isBrief?"brief":"details",
1858 // skip over command
1864 uint k = isVerbatimSection(data,i,len,endMarker);
1868 i=skipToEndMarker(data,k,len,endMarker);
1869 buf.addStr(data+orgPos,i-orgPos);
1870 // TODO: adjust lineNr
1879 else // not a command, just copy
1883 lineNr += (c=='\n') ? 1 : 0;
1886 len = static_cast<uint>(buf.getPos());
1892 //---------------------------------------------------------------------------
1894 IDocNodeASTPtr validatingParseDoc(IDocParser &parserIntf,
1895 const QCString &fileName,int startLine,
1896 const Definition *ctx,const MemberDef *md,
1897 const QCString &input,bool indexWords,
1898 bool isExample, const QCString &exampleName,
1899 bool singleLine, bool linkFromIndex,
1900 bool markdownSupport)
1902 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
1904 if (parser==0) return 0;
1905 //printf("validatingParseDoc(%s,%s)=[%s]\n",ctx?qPrint(ctx->name()):"<none>",
1906 // md?qPrint(md->name()):"<none>",
1908 //printf("========== validating %s at line %d\n",qPrint(fileName),startLine);
1909 //printf("---------------- input --------------------\n%s\n----------- end input -------------------\n",qPrint(input));
1910 //parser->context.token = new TokenInfo;
1912 // store parser state so we can re-enter this function if needed
1913 //bool fortranOpt = Config_getBool(OPTIMIZE_FOR_FORTRAN);
1914 parser->pushContext();
1916 if (ctx && ctx!=Doxygen::globalScope &&
1917 (ctx->definitionType()==Definition::TypeClass ||
1918 ctx->definitionType()==Definition::TypeNamespace
1922 parser->context.context = ctx->name();
1924 else if (ctx && ctx->definitionType()==Definition::TypePage)
1926 const Definition *scope = (toPageDef(ctx))->getPageScope();
1927 if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1929 else if (ctx && ctx->definitionType()==Definition::TypeGroup)
1931 const Definition *scope = (toGroupDef(ctx))->getGroupScope();
1932 if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1936 parser->context.context = "";
1938 parser->context.scope = ctx;
1940 if (indexWords && Doxygen::searchIndex)
1944 parser->context.searchUrl=md->getOutputFileBase();
1945 parser->searchData.setCurrentDoc(md,md->anchor(),false);
1949 parser->context.searchUrl=ctx->getOutputFileBase();
1950 parser->searchData.setCurrentDoc(ctx,ctx->anchor(),false);
1955 parser->context.searchUrl="";
1958 parser->context.fileName = fileName;
1959 parser->context.relPath = (!linkFromIndex && ctx) ?
1960 QCString(relativePathToRoot(ctx->getOutputFileBase())) :
1962 //printf("ctx->name=%s relPath=%s\n",qPrint(ctx->name()),qPrint(parser->context.relPath));
1963 parser->context.memberDef = md;
1964 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
1965 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
1966 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
1967 parser->context.inSeeBlock = FALSE;
1968 parser->context.xmlComment = FALSE;
1969 parser->context.insideHtmlLink = FALSE;
1970 parser->context.includeFileText = "";
1971 parser->context.includeFileOffset = 0;
1972 parser->context.includeFileLength = 0;
1973 parser->context.isExample = isExample;
1974 parser->context.exampleName = exampleName;
1975 parser->context.hasParamCommand = FALSE;
1976 parser->context.hasReturnCommand = FALSE;
1977 parser->context.retvalsFound.clear();
1978 parser->context.paramsFound.clear();
1979 parser->context.markdownSupport = markdownSupport;
1981 //printf("Starting comment block at %s:%d\n",qPrint(parser->context.fileName),startLine);
1982 parser->tokenizer.setLineNr(startLine);
1983 uint ioLen = static_cast<uint>(input.length());
1984 QCString inpStr = parser->processCopyDoc(input.data(),ioLen);
1985 if (inpStr.isEmpty() || inpStr.at(inpStr.length()-1)!='\n')
1989 //printf("processCopyDoc(in='%s' out='%s')\n",input,qPrint(inpStr));
1990 parser->tokenizer.init(inpStr.data(),parser->context.fileName,markdownSupport);
1992 // build abstract syntax tree
1993 auto ast = std::make_unique<DocNodeAST>(DocRoot(parser,md!=0,singleLine));
1994 std::get<DocRoot>(ast->root).parse(&ast->root);
1996 if (Debug::isFlagSet(Debug::PrintTree))
1998 // pretty print the result
1999 std::visit(PrintDocVisitor{},ast->root);
2002 parser->checkUnOrMultipleDocumentedParams();
2003 if (parser->context.memberDef) parser->context.memberDef->detectUndocumentedParams(parser->context.hasParamCommand,parser->context.hasReturnCommand);
2005 // TODO: These should be called at the end of the program.
2006 //parser->tokenizer.cleanup();
2007 //Mappers::cmdMapper->freeInstance();
2008 //Mappers::htmlTagMapper->freeInstance();
2010 // restore original parser state
2011 parser->popContext();
2013 //printf(">>>>>> end validatingParseDoc(%s,%s)\n",ctx?qPrint(ctx->name()):"<none>",
2014 // md?qPrint(md->name()):"<none>");
2019 IDocNodeASTPtr validatingParseText(IDocParser &parserIntf,const QCString &input)
2021 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2023 if (parser==0) return 0;
2025 // store parser state so we can re-enter this function if needed
2026 parser->pushContext();
2028 //printf("------------ input ---------\n%s\n"
2029 // "------------ end input -----\n",input);
2030 //parser->context.token = new TokenInfo;
2031 parser->context.context = "";
2032 parser->context.fileName = "<parseText>";
2033 parser->context.relPath = "";
2034 parser->context.memberDef = 0;
2035 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
2036 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
2037 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
2038 parser->context.inSeeBlock = FALSE;
2039 parser->context.xmlComment = FALSE;
2040 parser->context.insideHtmlLink = FALSE;
2041 parser->context.includeFileText = "";
2042 parser->context.includeFileOffset = 0;
2043 parser->context.includeFileLength = 0;
2044 parser->context.isExample = FALSE;
2045 parser->context.exampleName = "";
2046 parser->context.hasParamCommand = FALSE;
2047 parser->context.hasReturnCommand = FALSE;
2048 parser->context.retvalsFound.clear();
2049 parser->context.paramsFound.clear();
2050 parser->context.searchUrl="";
2053 auto ast = std::make_unique<DocNodeAST>(DocText(parser));
2055 if (!input.isEmpty())
2057 parser->tokenizer.setLineNr(1);
2058 parser->tokenizer.init(input.data(),parser->context.fileName,Config_getBool(MARKDOWN_SUPPORT));
2060 // build abstract syntax tree
2061 std::get<DocText>(ast->root).parse(&ast->root);
2063 if (Debug::isFlagSet(Debug::PrintTree))
2065 // pretty print the result
2066 std::visit(PrintDocVisitor{},ast->root);
2070 // restore original parser state
2071 parser->popContext();
2075 IDocNodeASTPtr createRef(IDocParser &parserIntf,const QCString &target,const QCString &context)
2077 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2079 if (parser==0) return 0;
2080 return std::make_unique<DocNodeAST>(DocRef(parser,0,target,context));
2083 void docFindSections(const QCString &input,
2084 const Definition *d,
2085 const QCString &fileName)
2088 parser.tokenizer.findSections(input,d,fileName);