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 #if !ENABLE_DOCPARSER_TRACING
45 #undef AUTO_TRACE_EXIT
46 #define AUTO_TRACE(...) (void)0
47 #define AUTO_TRACE_ADD(...) (void)0
48 #define AUTO_TRACE_EXIT(...) (void)0
52 //---------------------------------------------------------------------------
54 IDocParserPtr createDocParser()
56 return std::make_unique<DocParser>();
59 //---------------------------------------------------------------------------
60 DocParser::~DocParser()
64 void DocParser::pushContext()
67 //indent.fill(' ',contextStack.size()*2+2);
68 //printf("%sdocParserPushContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
70 tokenizer.pushContext();
71 contextStack.push(DocParserContext());
72 auto &ctx = contextStack.top();
74 ctx.lineNo = tokenizer.getLineNr();
75 context.token = tokenizer.token();
78 void DocParser::popContext()
80 auto &ctx = contextStack.top();
82 tokenizer.setLineNr(ctx.lineNo);
84 tokenizer.popContext();
85 context.token = tokenizer.token();
88 //indent.fill(' ',contextStack.size()*2+2);
89 //printf("%sdocParserPopContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
92 //---------------------------------------------------------------------------
94 /*! search for an image in the imageNameDict and if found
95 * copies the image to the output directory (which depends on the \a type
98 QCString DocParser::findAndCopyImage(const QCString &fileName, DocImage::Type type, bool doWarn)
102 FileDef *fd = findFileDef(Doxygen::imageNameLinkedMap,fileName,ambig);
103 //printf("Search for %s\n",fileName);
109 text.sprintf("image file name %s is ambiguous.\n",qPrint(fileName));
110 text+="Possible candidates:\n";
111 text+=showFileDefMatches(Doxygen::imageNameLinkedMap,fileName);
112 warn_doc_error(context.fileName,tokenizer.getLineNr(),"%s", qPrint(text));
115 QCString inputFile = fd->absFilePath();
116 FileInfo infi(inputFile.str());
121 if ((i=result.findRev('/'))!=-1 || (i=result.findRev('\\'))!=-1)
123 result = result.right(static_cast<int>(result.length())-i-1);
125 //printf("fileName=%s result=%s\n",fileName,qPrint(result));
130 if (!Config_getBool(GENERATE_HTML)) return result;
131 outputDir = Config_getString(HTML_OUTPUT);
133 case DocImage::Latex:
134 if (!Config_getBool(GENERATE_LATEX)) return result;
135 outputDir = Config_getString(LATEX_OUTPUT);
137 case DocImage::DocBook:
138 if (!Config_getBool(GENERATE_DOCBOOK)) return result;
139 outputDir = Config_getString(DOCBOOK_OUTPUT);
142 if (!Config_getBool(GENERATE_RTF)) return result;
143 outputDir = Config_getString(RTF_OUTPUT);
146 if (!Config_getBool(GENERATE_XML)) return result;
147 outputDir = Config_getString(XML_OUTPUT);
150 QCString outputFile = outputDir+"/"+result;
151 FileInfo outfi(outputFile.str());
152 if (outfi.isSymLink())
154 Dir().remove(outputFile.str());
155 warn_doc_error(context.fileName,tokenizer.getLineNr(),
156 "destination of image %s is a symlink, replacing with image",
159 if (outputFile!=inputFile) // prevent copying to ourself
161 if (copyFile(inputFile,outputFile) && type==DocImage::Html)
163 Doxygen::indexList->addImageFile(result);
169 warn_doc_error(context.fileName,tokenizer.getLineNr(),
170 "could not open image %s",qPrint(fileName));
173 if (type==DocImage::Latex && Config_getBool(USE_PDFLATEX) &&
174 fd->name().endsWith(".eps")
176 { // we have an .eps image in pdflatex mode => convert it to a pdf.
177 QCString outputDir = Config_getString(LATEX_OUTPUT);
178 QCString baseName = fd->name().left(fd->name().length()-4);
179 QCString epstopdfArgs(4096);
180 epstopdfArgs.sprintf("\"%s/%s.eps\" --outfile=\"%s/%s.pdf\"",
181 qPrint(outputDir), qPrint(baseName),
182 qPrint(outputDir), qPrint(baseName));
183 if (Portable::system("epstopdf",epstopdfArgs)!=0)
185 err("Problems running epstopdf. Check your TeX installation!\n");
193 if (!result.startsWith("http:") && !result.startsWith("https:") && doWarn)
195 warn_doc_error(context.fileName,tokenizer.getLineNr(),
196 "image file %s is not found in IMAGE_PATH: "
197 "assuming external image.",qPrint(fileName)
204 /*! Collects the parameters found with \@param command
205 * in a list context.paramsFound. If
206 * the parameter is not an actual parameter of the current
207 * member context.memberDef, then a warning is raised (unless warnings
208 * are disabled altogether).
210 void DocParser::checkArgumentName()
212 if (!(Config_getBool(WARN_IF_DOC_ERROR) || Config_getBool(WARN_IF_INCOMPLETE_DOC))) return;
213 if (context.memberDef==0) return; // not a member
214 std::string name = context.token->name.str();
215 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
216 context.memberDef->argumentList() :
217 context.memberDef->declArgumentList();
218 SrcLangExt lang = context.memberDef->getLanguage();
219 //printf("isDocsForDefinition()=%d\n",context.memberDef->isDocsForDefinition());
220 if (al.empty()) return; // no argument list
222 static const reg::Ex re(R"(\$?\w+\.*)");
223 reg::Iterator it(name,re);
225 for (; it!=end ; ++it)
227 const auto &match = *it;
228 QCString aName=match.str();
229 if (lang==SrcLangExt_Fortran) aName=aName.lower();
230 //printf("aName='%s'\n",qPrint(aName));
232 for (const Argument &a : al)
234 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
235 if (lang==SrcLangExt_Fortran) argName=argName.lower();
236 argName=argName.stripWhiteSpace();
237 //printf("argName='%s' aName=%s\n",qPrint(argName),qPrint(aName));
238 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
241 context.paramsFound.insert(aName.str());
248 //printf("member type=%d\n",context.memberDef->memberType());
249 QCString scope=context.memberDef->getScopeString();
250 if (!scope.isEmpty()) scope+="::"; else scope="";
251 QCString inheritedFrom = "";
252 QCString docFile = context.memberDef->docFile();
253 int docLine = context.memberDef->docLine();
254 const MemberDef *inheritedMd = context.memberDef->inheritsDocsFrom();
255 if (inheritedMd) // documentation was inherited
257 inheritedFrom.sprintf(" inherited from member %s at line "
258 "%d in file %s",qPrint(inheritedMd->name()),
259 inheritedMd->docLine(),qPrint(inheritedMd->docFile()));
260 docFile = context.memberDef->getDefFileName();
261 docLine = context.memberDef->getDefLine();
263 QCString alStr = argListToString(al);
264 warn_doc_error(docFile,docLine,
265 "argument '%s' of command @param "
266 "is not found in the argument list of %s%s%s%s",
267 qPrint(aName), qPrint(scope), qPrint(context.memberDef->name()),
268 qPrint(alStr), qPrint(inheritedFrom));
272 /*! Collects the return values found with \@retval command
273 * in a global list g_parserContext.retvalsFound.
275 void DocParser::checkRetvalName()
277 QCString name = context.token->name;
278 if (!Config_getBool(WARN_IF_DOC_ERROR)) return;
279 if (context.memberDef==0 || name.isEmpty()) return; // not a member or no valid name
280 if (context.retvalsFound.count(name.str())==1) // only report the first double entry
282 warn_doc_error(context.memberDef->getDefFileName(),
283 context.memberDef->getDefLine(),
285 qPrint("return value '" + name + "' of " +
286 QCString(context.memberDef->qualifiedName()) +
287 " has multiple documentation sections"));
289 context.retvalsFound.insert(name.str());
292 /*! Checks if the parameters that have been specified using \@param are
293 * indeed all parameters and that a parameter does not have multiple
295 * Must be called after checkArgumentName() has been called for each
298 void DocParser::checkUnOrMultipleDocumentedParams()
300 if (context.memberDef && context.hasParamCommand)
302 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
303 context.memberDef->argumentList() :
304 context.memberDef->declArgumentList();
305 SrcLangExt lang = context.memberDef->getLanguage();
308 ArgumentList undocParams;
309 for (const Argument &a: al)
311 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
312 if (lang==SrcLangExt_Fortran) argName = argName.lower();
313 argName=argName.stripWhiteSpace();
314 QCString aName = argName;
315 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
316 if (lang==SrcLangExt_Python && (argName=="self" || argName=="cls"))
318 // allow undocumented self / cls parameter for Python
320 else if (!argName.isEmpty())
322 size_t count = context.paramsFound.count(argName.str());
323 if (count==0 && a.docs.isEmpty())
325 undocParams.push_back(a);
327 else if (count>1 && Config_getBool(WARN_IF_DOC_ERROR))
329 warn_doc_error(context.memberDef->docFile(),
330 context.memberDef->docLine(),
332 qPrint("argument '" + aName +
333 "' from the argument list of " +
334 QCString(context.memberDef->qualifiedName()) +
335 " has multiple @param documentation sections"));
339 if (!undocParams.empty() && Config_getBool(WARN_IF_INCOMPLETE_DOC))
342 QCString errMsg = "The following parameter";
343 if (undocParams.size()>1) errMsg+="s";
345 QCString(context.memberDef->qualifiedName()) +
346 QCString(argListToString(al)) +
347 (undocParams.size()>1 ? " are" : " is") + " not documented:\n";
348 for (const Argument &a : undocParams)
350 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
351 if (lang==SrcLangExt_Fortran) argName = argName.lower();
352 argName=argName.stripWhiteSpace();
353 if (!first) errMsg+="\n";
355 errMsg+=" parameter '"+argName+"'";
357 warn_incomplete_doc(context.memberDef->docFile(),
358 context.memberDef->docLine(),
360 qPrint(substitute(errMsg,"%","%%")));
365 if (context.paramsFound.empty() && Config_getBool(WARN_IF_DOC_ERROR))
367 warn_doc_error(context.memberDef->docFile(),
368 context.memberDef->docLine(),
370 qPrint(context.memberDef->qualifiedName() +
371 " has @param documentation sections but no arguments"));
378 //---------------------------------------------------------------------------
380 //---------------------------------------------------------------------------
382 //---------------------------------------------------------------------------
383 /*! Looks for a documentation block with name commandName in the current
384 * context (g_parserContext.context). The resulting documentation string is
385 * put in pDoc, the definition in which the documentation was found is
387 * @retval TRUE if name was found.
388 * @retval FALSE if name was not found.
390 bool DocParser::findDocsForMemberOrCompound(const QCString &commandName,
393 const Definition **pDef)
395 //printf("findDocsForMemberOrCompound(%s)\n",commandName);
399 QCString cmdArg=commandName;
400 if (cmdArg.isEmpty()) return FALSE;
403 const GroupDef *gd=0;
405 gd = Doxygen::groupLinkedMap->find(cmdArg);
408 *pDoc=gd->documentation();
409 *pBrief=gd->briefDescription();
413 pd = Doxygen::pageLinkedMap->find(cmdArg);
416 *pDoc=pd->documentation();
417 *pBrief=pd->briefDescription();
422 fd = findFileDef(Doxygen::inputNameLinkedMap,cmdArg,ambig);
423 if (fd && !ambig) // file
425 *pDoc=fd->documentation();
426 *pBrief=fd->briefDescription();
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);
436 cmdArg.startsWith("anonymous_namespace{")
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;
447 cmdArg = substitute(cmdArg,".","::");
450 int l=static_cast<int>(cmdArg.length());
452 int funcStart=cmdArg.find('(');
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)
465 if (leftParen<secondParen)
467 funcStart=secondParen;
472 QCString name=removeRedundantWhiteSpace(cmdArg.left(funcStart));
473 QCString args=cmdArg.right(l-funcStart);
474 // try if the link is to a member
476 context.context.find('.')==-1 ? context.context : QCString(), // find('.') is a hack to detect files
478 args.isEmpty() ? QCString() : args);
480 GetDefResult result = getDefs(input);
481 //printf("found=%d context=%s name=%s\n",found,qPrint(context.context),qPrint(name));
482 if (result.found && result.md)
484 *pDoc=result.md->documentation();
485 *pBrief=result.md->briefDescription();
490 int scopeOffset=static_cast<int>(context.context.length());
493 QCString fullName=cmdArg;
496 fullName.prepend(context.context.left(scopeOffset)+"::");
498 //printf("Trying fullName='%s'\n",qPrint(fullName));
500 // try class, namespace, group, page, file reference
501 const ClassDef *cd = Doxygen::classLinkedMap->find(fullName);
504 *pDoc=cd->documentation();
505 *pBrief=cd->briefDescription();
509 const NamespaceDef *nd = Doxygen::namespaceLinkedMap->find(fullName);
512 *pDoc=nd->documentation();
513 *pBrief=nd->briefDescription();
523 scopeOffset = context.context.findRev("::",scopeOffset-1);
524 if (scopeOffset==-1) scopeOffset=0;
526 } while (scopeOffset>=0);
532 //---------------------------------------------------------------------------
533 void DocParser::errorHandleDefaultToken(DocNodeVariant *parent,int tok,
534 DocNodeList &children,const QCString &txt)
536 const char *cmd_start = "\\";
543 children.append<DocWord>(this,parent,TK_COMMAND_CHAR(tok) + context.token->name);
544 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Illegal command %s found as part of a %s",
545 qPrint(cmd_start + context.token->name),qPrint(txt));
548 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported symbol %s found as part of a %s",
549 qPrint(context.token->name), qPrint(txt));
552 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported HTML tag <%s%s> found as part of a %s",
553 context.token->endTag ? "/" : "",qPrint(context.token->name), qPrint(txt));
556 children.append<DocWord>(this,parent,context.token->name);
557 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unexpected token %s found as part of a %s",
558 DocTokenizer::tokToString(tok), qPrint(txt));
563 //---------------------------------------------------------------------------
565 int DocParser::handleStyleArgument(DocNodeVariant *parent,DocNodeList &children,const QCString &cmdName)
567 AUTO_TRACE("cmdName={}",cmdName);
568 QCString saveCmdName = cmdName;
569 int tok=tokenizer.lex();
570 if (tok!=TK_WHITESPACE)
572 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
573 qPrint(saveCmdName));
576 while ((tok=tokenizer.lex()) &&
577 tok!=TK_WHITESPACE &&
583 static const reg::Ex specialChar(R"([.,|()\[\]:;?])");
584 if (tok==TK_WORD && context.token->name.length()==1 &&
585 reg::match(context.token->name.str(),specialChar))
587 // special character that ends the markup command
590 if (!defaultHandleToken(parent,tok,children))
595 if (insideLI(parent) && Mappers::htmlTagMapper->map(context.token->name) && context.token->endTag)
596 { // ignore </li> as the end of a style command
599 AUTO_TRACE_EXIT("end tok={}",DocTokenizer::tokToString(tok));
603 errorHandleDefaultToken(parent,tok,children,"\\" + saveCmdName + " command");
609 AUTO_TRACE_EXIT("end tok={}",DocTokenizer::tokToString(tok));
610 return (tok==TK_NEWPARA || tok==TK_LISTITEM || tok==TK_ENDLIST
614 /*! Called when a style change starts. For instance a \<b\> command is
617 void DocParser::handleStyleEnter(DocNodeVariant *parent,DocNodeList &children,
618 DocStyleChange::Style s,const QCString &tagName,const HtmlAttribList *attribs)
620 AUTO_TRACE("tagName={}",tagName);
621 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),s,tagName,TRUE,attribs);
622 context.styleStack.push(&children.back());
625 /*! Called when a style change ends. For instance a \</b\> command is
628 void DocParser::handleStyleLeave(DocNodeVariant *parent,DocNodeList &children,
629 DocStyleChange::Style s,const QCString &tagName)
631 AUTO_TRACE("tagName={}",tagName);
632 QCString tagNameLower = QCString(tagName).lower();
634 auto topStyleChange = [](const DocStyleChangeStack &stack) -> const DocStyleChange &
636 return std::get<DocStyleChange>(*stack.top());
639 if (context.styleStack.empty() || // no style change
640 topStyleChange(context.styleStack).style()!=s || // wrong style change
641 topStyleChange(context.styleStack).tagName()!=tagNameLower || // wrong style change
642 topStyleChange(context.styleStack).position()!=context.nodeStack.size() // wrong position
645 if (context.styleStack.empty())
647 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag without matching <%s>",
648 qPrint(tagName),qPrint(tagName));
650 else if (topStyleChange(context.styleStack).tagName()!=tagNameLower)
652 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
653 qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
655 else if (topStyleChange(context.styleStack).style()!=s)
657 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
658 qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
662 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> at different nesting level (%zu) than expected (%zu)",
663 qPrint(tagName),context.nodeStack.size(),topStyleChange(context.styleStack).position());
666 else // end the section
668 children.append<DocStyleChange>(
669 this,parent,context.nodeStack.size(),s,
670 topStyleChange(context.styleStack).tagName(),FALSE);
671 context.styleStack.pop();
675 /*! Called at the end of a paragraph to close all open style changes
676 * (e.g. a <b> without a </b>). The closed styles are pushed onto a stack
677 * and entered again at the start of a new paragraph.
679 void DocParser::handlePendingStyleCommands(DocNodeVariant *parent,DocNodeList &children)
682 if (!context.styleStack.empty())
684 const DocStyleChange *sc = &std::get<DocStyleChange>(*context.styleStack.top());
685 while (sc && sc->position()>=context.nodeStack.size())
686 { // there are unclosed style modifiers in the paragraph
687 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),
688 sc->style(),sc->tagName(),FALSE);
689 context.initialStyleStack.push(context.styleStack.top());
690 context.styleStack.pop();
691 sc = !context.styleStack.empty() ? &std::get<DocStyleChange>(*context.styleStack.top()) : 0;
696 void DocParser::handleInitialStyleCommands(DocNodeVariant *parent,DocNodeList &children)
699 while (!context.initialStyleStack.empty())
701 const DocStyleChange &sc = std::get<DocStyleChange>(*context.initialStyleStack.top());
702 handleStyleEnter(parent,children,sc.style(),sc.tagName(),&sc.attribs());
703 context.initialStyleStack.pop();
707 int DocParser::handleAHref(DocNodeVariant *parent,DocNodeList &children,
708 const HtmlAttribList &tagHtmlAttribs)
712 int retval = RetVal_OK;
713 for (const auto &opt : tagHtmlAttribs)
715 if (opt.name=="name" || opt.name=="id") // <a name=label> or <a id=label> tag
717 if (!opt.value.isEmpty())
719 children.append<DocAnchor>(this,parent,opt.value,TRUE);
720 break; // stop looking for other tag attribs
724 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <a> tag with name option but without value!");
727 else if (opt.name=="href") // <a href=url>..</a> tag
730 HtmlAttribList attrList = tagHtmlAttribs;
731 // and remove the href attribute
732 attrList.erase(attrList.begin()+index);
734 if (opt.value.at(0) != '#') relPath = context.relPath;
735 children.append<DocHRef>(this, parent, attrList,
737 convertNameToFile(context.fileName, FALSE, TRUE));
738 context.insideHtmlLink=TRUE;
739 retval = children.get_last<DocHRef>()->parse();
740 context.insideHtmlLink=FALSE;
743 else // unsupported option for tag a
751 void DocParser::handleUnclosedStyleCommands()
754 if (!context.initialStyleStack.empty())
756 QCString tagName = std::get<DocStyleChange>(*context.initialStyleStack.top()).tagName();
757 context.initialStyleStack.pop();
758 handleUnclosedStyleCommands();
759 warn_doc_error(context.fileName,tokenizer.getLineNr(),
760 "end of comment block while expecting "
761 "command </%s>",qPrint(tagName));
765 void DocParser::handleLinkedWord(DocNodeVariant *parent,DocNodeList &children,bool ignoreAutoLinkFlag)
767 QCString name = linkToText(SrcLangExt_Unknown,context.token->name,TRUE);
768 AUTO_TRACE("word={}",name);
769 bool autolinkSupport = Config_getBool(AUTOLINK_SUPPORT);
770 if (!autolinkSupport && !ignoreAutoLinkFlag) // no autolinking -> add as normal word
772 children.append<DocWord>(this,parent,name);
776 // ------- try to turn the word 'name' into a link
778 const Definition *compound=0;
779 const MemberDef *member=0;
780 uint32_t len = context.token->name.length();
783 FileDef *fd = findFileDef(Doxygen::inputNameLinkedMap,context.fileName,ambig);
784 //printf("handleLinkedWord(%s) context.context=%s\n",qPrint(context.token->name),qPrint(context.context));
785 if (!context.insideHtmlLink &&
786 (resolveRef(context.context,context.token->name,context.inSeeBlock,&compound,&member,TRUE,fd,TRUE)
787 || (!context.context.isEmpty() && // also try with global scope
788 resolveRef("",context.token->name,context.inSeeBlock,&compound,&member,FALSE,0,TRUE))
792 //printf("resolveRef %s = %p (linkable?=%d)\n",qPrint(context.token->name),member,member ? member->isLinkable() : FALSE);
793 if (member && member->isLinkable()) // member link
795 if (member->isObjCMethod())
797 bool localLink = context.memberDef ? member->getClassDef()==context.memberDef->getClassDef() : FALSE;
798 name = member->objCMethodName(localLink,context.inSeeBlock);
800 children.append<DocLinkedWord>(
802 member->getReference(),
803 member->getOutputFileBase(),
805 member->briefDescriptionAsTooltip());
807 else if (compound->isLinkable()) // compound link
809 QCString anchor = compound->anchor();
810 if (compound->definitionType()==Definition::TypeFile)
812 name=context.token->name;
814 else if (compound->definitionType()==Definition::TypeGroup)
816 name=toGroupDef(compound)->groupTitle();
818 children.append<DocLinkedWord>(
820 compound->getReference(),
821 compound->getOutputFileBase(),
823 compound->briefDescriptionAsTooltip());
825 else if (compound->definitionType()==Definition::TypeFile &&
826 (toFileDef(compound))->generateSourceFile()
827 ) // undocumented file that has source code we can link to
829 children.append<DocLinkedWord>(
830 this,parent,context.token->name,
831 compound->getReference(),
832 compound->getSourceFileBase(),
834 compound->briefDescriptionAsTooltip());
838 children.append<DocWord>(this,parent,name);
841 else if (!context.insideHtmlLink && len>1 && context.token->name.at(len-1)==':')
843 // special case, where matching Foo: fails to be an Obj-C reference,
844 // but Foo itself might be linkable.
845 context.token->name=context.token->name.left(len-1);
846 handleLinkedWord(parent,children,ignoreAutoLinkFlag);
847 children.append<DocWord>(this,parent,":");
849 else if (!context.insideHtmlLink && (cd=getClass(context.token->name+"-p")))
851 // special case 2, where the token name is not a class, but could
852 // be a Obj-C protocol
853 children.append<DocLinkedWord>(
856 cd->getOutputFileBase(),
858 cd->briefDescriptionAsTooltip());
860 else // normal non-linkable word
862 if (context.token->name.startsWith("#") || context.token->name.startsWith("::"))
864 warn_doc_error(context.fileName,tokenizer.getLineNr(),"explicit link request to '%s' could not be resolved",qPrint(name));
865 children.append<DocWord>(this,parent,context.token->name);
869 children.append<DocWord>(this,parent,name);
874 void DocParser::handleParameterType(DocNodeVariant *parent,DocNodeList &children,const QCString ¶mTypes)
876 QCString name = context.token->name; // save token name
877 AUTO_TRACE("name={}",name);
880 while ((i=paramTypes.find('|',p))!=-1)
882 name1 = paramTypes.mid(p,i-p);
884 context.token->name=ii!=-1 ? name1.mid(0,ii) : name1; // take part without []
885 handleLinkedWord(parent,children);
886 if (ii!=-1) children.append<DocWord>(this,parent,name1.mid(ii)); // add [] part
888 children.append<DocSeparator>(this,parent,"|");
890 name1 = paramTypes.mid(p);
892 context.token->name=ii!=-1 ? name1.mid(0,ii) : name1;
893 handleLinkedWord(parent,children);
894 if (ii!=-1) children.append<DocWord>(this,parent,name1.mid(ii));
895 context.token->name = name; // restore original token name
898 void DocParser::handleInternalRef(DocNodeVariant *parent,DocNodeList &children)
900 int tok=tokenizer.lex();
901 QCString tokenName = context.token->name;
902 AUTO_TRACE("name={}",tokenName);
903 if (tok!=TK_WHITESPACE)
905 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
909 tokenizer.setStateInternalRef();
910 tok=tokenizer.lex(); // get the reference id
911 if (tok!=TK_WORD && tok!=TK_LNKWORD)
913 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
914 DocTokenizer::tokToString(tok),qPrint(tokenName));
917 children.append<DocInternalRef>(this,parent,context.token->name);
918 children.get_last<DocInternalRef>()->parse();
921 void DocParser::handleAnchor(DocNodeVariant *parent,DocNodeList &children)
924 int tok=tokenizer.lex();
925 if (tok!=TK_WHITESPACE)
927 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
928 qPrint(context.token->name));
931 tokenizer.setStateAnchor();
935 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected end of comment block while parsing the "
936 "argument of command %s",qPrint(context.token->name));
939 else if (tok!=TK_WORD && tok!=TK_LNKWORD)
941 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
942 DocTokenizer::tokToString(tok),qPrint(context.token->name));
945 tokenizer.setStatePara();
946 children.append<DocAnchor>(this,parent,context.token->name,FALSE);
950 /* Helper function that deals with the title, width, and height arguments of various commands.
951 * @param[in] cmd Command id for which to extract caption and size info.
952 * @param[in] parent Parent node, owner of the children list passed as
953 * the third argument.
954 * @param[in] children The list of child nodes to which the node representing
955 * the token can be added.
956 * @param[out] width the extracted width specifier
957 * @param[out] height the extracted height specifier
959 void DocParser::defaultHandleTitleAndSize(const int cmd, DocNodeVariant *parent, DocNodeList &children, QCString &width,QCString &height)
962 auto ns = AutoNodeStack(this,parent);
965 tokenizer.setStateTitle();
967 while ((tok=tokenizer.lex()))
969 if (tok==TK_WORD && (context.token->name=="width=" || context.token->name=="height="))
971 // special case: no title, but we do have a size indicator
974 else if (tok==TK_HTMLTAG)
976 tokenizer.unputString(context.token->name);
979 if (!defaultHandleToken(parent,tok,children))
981 errorHandleDefaultToken(parent,tok,children,Mappers::cmdMapper->find(cmd));
984 // parse size attributes
989 while (tok==TK_WHITESPACE || tok==TK_WORD) // there are values following the title
993 if (context.token->name=="width=" || context.token->name=="height=")
995 tokenizer.setStateTitleAttrValue();
996 context.token->name = context.token->name.left(context.token->name.length()-1);
999 if (context.token->name=="width")
1001 width = context.token->chars;
1003 else if (context.token->name=="height")
1005 height = context.token->chars;
1009 tokenizer.unputString(context.token->name);
1010 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unknown option '%s' after \\%s command, expected 'width' or 'height'",
1011 qPrint(context.token->name), qPrint(Mappers::cmdMapper->find(cmd)));
1016 tok=tokenizer.lex();
1017 // if we found something we did not expect, push it back to the stream
1018 // so it can still be processed
1019 if (tok==TK_COMMAND_AT || tok==TK_COMMAND_BS)
1021 tokenizer.unputString(context.token->name);
1022 tokenizer.unputString(tok==TK_COMMAND_AT ? "@" : "\\");
1024 else if (tok==TK_SYMBOL || tok==TK_HTMLTAG)
1026 tokenizer.unputString(context.token->name);
1029 tokenizer.setStatePara();
1031 handlePendingStyleCommands(parent,children);
1032 AUTO_TRACE_EXIT("width={} height={}",width,height);
1035 void DocParser::handleImage(DocNodeVariant *parent, DocNodeList &children)
1038 bool inlineImage = false;
1041 int tok=tokenizer.lex();
1042 if (tok!=TK_WHITESPACE)
1046 if (context.token->name == "{")
1048 tokenizer.setStateOptions();
1050 tokenizer.setStatePara();
1051 StringVector optList=split(context.token->name.str(),",");
1052 for (const auto &opt : optList)
1054 if (opt.empty()) continue;
1055 QCString locOpt(opt);
1057 locOpt = locOpt.stripWhiteSpace();
1058 locOptLow = locOpt.lower();
1059 if (locOptLow == "inline")
1063 else if (locOptLow.startsWith("anchor:"))
1065 if (!anchorStr.isEmpty())
1067 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1068 "multiple use of option 'anchor' for 'image' command, ignoring: '%s'",
1069 qPrint(locOpt.mid(7)));
1073 anchorStr = locOpt.mid(7);
1078 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1079 "unknown option '%s' for 'image' command specified",
1083 tok=tokenizer.lex();
1084 if (tok!=TK_WHITESPACE)
1086 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1093 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1097 tok=tokenizer.lex();
1098 if (tok!=TK_WORD && tok!=TK_LNKWORD)
1100 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1101 DocTokenizer::tokToString(tok));
1104 tok=tokenizer.lex();
1105 if (tok!=TK_WHITESPACE)
1107 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1111 QCString imgType = context.token->name.lower();
1112 if (imgType=="html") t=DocImage::Html;
1113 else if (imgType=="latex") t=DocImage::Latex;
1114 else if (imgType=="docbook") t=DocImage::DocBook;
1115 else if (imgType=="rtf") t=DocImage::Rtf;
1116 else if (imgType=="xml") t=DocImage::Xml;
1119 warn_doc_error(context.fileName,tokenizer.getLineNr(),"output format `%s` specified as the first argument of "
1120 "\\image command is not valid",
1124 tokenizer.setStateFile();
1125 tok=tokenizer.lex();
1126 tokenizer.setStatePara();
1129 warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1130 DocTokenizer::tokToString(tok));
1133 if (!anchorStr.isEmpty())
1135 children.append<DocAnchor>(this,parent,anchorStr,true);
1137 HtmlAttribList attrList;
1138 children.append<DocImage>(this,parent,attrList,
1139 findAndCopyImage(context.token->name,t),t,"",inlineImage);
1140 children.get_last<DocImage>()->parse();
1144 /* Helper function that deals with the most common tokens allowed in
1145 * title like sections.
1146 * @param parent Parent node, owner of the children list passed as
1147 * the third argument.
1148 * @param tok The token to process.
1149 * @param children The list of child nodes to which the node representing
1150 * the token can be added.
1151 * @param handleWord Indicates if word token should be processed
1152 * @retval TRUE The token was handled.
1153 * @retval FALSE The token was not handled.
1155 bool DocParser::defaultHandleToken(DocNodeVariant *parent,int tok, DocNodeList &children,bool handleWord)
1157 AUTO_TRACE("token={} handleWord={}",DocTokenizer::tokToString(tok),handleWord);
1158 if (tok==TK_WORD || tok==TK_LNKWORD || tok==TK_SYMBOL || tok==TK_URL ||
1159 tok==TK_COMMAND_AT || tok==TK_COMMAND_BS || tok==TK_HTMLTAG
1164 QCString tokenName = context.token->name;
1165 AUTO_TRACE_ADD("tokenName={}",tokenName);
1171 switch (Mappers::cmdMapper->map(tokenName))
1174 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_BSlash);
1177 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_At);
1180 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Less);
1183 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Greater);
1186 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Amp);
1189 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dollar);
1192 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Hash);
1195 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_DoubleColon);
1198 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Percent);
1201 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1202 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1205 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1206 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1207 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1210 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Quot);
1213 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dot);
1216 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Plus);
1219 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1222 children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Equal);
1226 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Italic,tokenName,TRUE);
1227 tok=handleStyleArgument(parent,children,tokenName);
1228 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Italic,tokenName,FALSE);
1229 if (tok!=TK_WORD) children.append<DocWhiteSpace>(this,parent," ");
1230 if (tok==TK_NEWPARA) goto handlepara;
1231 else if (tok==TK_WORD || tok==TK_HTMLTAG)
1233 AUTO_TRACE_ADD("CMD_EMPHASIS: reparsing");
1240 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Bold,tokenName,TRUE);
1241 tok=handleStyleArgument(parent,children,tokenName);
1242 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Bold,tokenName,FALSE);
1243 if (tok!=TK_WORD) children.append<DocWhiteSpace>(this,parent," ");
1244 if (tok==TK_NEWPARA) goto handlepara;
1245 else if (tok==TK_WORD || tok==TK_HTMLTAG)
1247 AUTO_TRACE_ADD("CMD_BOLD: reparsing");
1254 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Code,tokenName,TRUE);
1255 tok=handleStyleArgument(parent,children,tokenName);
1256 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),DocStyleChange::Code,tokenName,FALSE);
1257 if (tok!=TK_WORD) children.append<DocWhiteSpace>(this,parent," ");
1258 if (tok==TK_NEWPARA) goto handlepara;
1259 else if (tok==TK_WORD || tok==TK_HTMLTAG)
1261 AUTO_TRACE_ADD("CMD_CODE: reparsing");
1268 tokenizer.setStateHtmlOnly();
1269 tok = tokenizer.lex();
1270 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::HtmlOnly,context.isExample,context.exampleName,context.token->name=="block");
1271 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"htmlonly section ended without end marker");
1272 tokenizer.setStatePara();
1277 tokenizer.setStateManOnly();
1278 tok = tokenizer.lex();
1279 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::ManOnly,context.isExample,context.exampleName);
1280 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"manonly section ended without end marker");
1281 tokenizer.setStatePara();
1286 tokenizer.setStateRtfOnly();
1287 tok = tokenizer.lex();
1288 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::RtfOnly,context.isExample,context.exampleName);
1289 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"rtfonly section ended without end marker");
1290 tokenizer.setStatePara();
1295 tokenizer.setStateLatexOnly();
1296 tok = tokenizer.lex();
1297 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::LatexOnly,context.isExample,context.exampleName);
1298 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"latexonly section ended without end marker");
1299 tokenizer.setStatePara();
1304 tokenizer.setStateXmlOnly();
1305 tok = tokenizer.lex();
1306 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::XmlOnly,context.isExample,context.exampleName);
1307 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"xmlonly section ended without end marker");
1308 tokenizer.setStatePara();
1313 tokenizer.setStateDbOnly();
1314 tok = tokenizer.lex();
1315 children.append<DocVerbatim>(this,parent,context.context,context.token->verb,DocVerbatim::DocbookOnly,context.isExample,context.exampleName);
1316 if (tok==0) warn_doc_error(context.fileName,tokenizer.getLineNr(),"docbookonly section ended without end marker");
1317 tokenizer.setStatePara();
1322 children.append<DocFormula>(this,parent,context.token->id);
1328 handleAnchor(parent,children);
1331 case CMD_INTERNALREF:
1333 handleInternalRef(parent,children);
1334 tokenizer.setStatePara();
1340 tokenizer.setStateSetScope();
1341 (void)tokenizer.lex();
1342 scope = context.token->name;
1343 context.context = scope;
1344 //printf("Found scope='%s'\n",qPrint(scope));
1345 tokenizer.setStatePara();
1349 handleImage(parent,children);
1357 switch (Mappers::htmlTagMapper->map(tokenName))
1360 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <div> tag in heading");
1363 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <pre> tag in heading");
1366 if (!context.token->endTag)
1368 handleStyleEnter(parent,children,DocStyleChange::Bold,tokenName,&context.token->attribs);
1372 handleStyleLeave(parent,children,DocStyleChange::Bold,tokenName);
1376 if (!context.token->endTag)
1378 handleStyleEnter(parent,children,DocStyleChange::S,tokenName,&context.token->attribs);
1382 handleStyleLeave(parent,children,DocStyleChange::S,tokenName);
1386 if (!context.token->endTag)
1388 handleStyleEnter(parent,children,DocStyleChange::Strike,tokenName,&context.token->attribs);
1392 handleStyleLeave(parent,children,DocStyleChange::Strike,tokenName);
1396 if (!context.token->endTag)
1398 handleStyleEnter(parent,children,DocStyleChange::Del,tokenName,&context.token->attribs);
1402 handleStyleLeave(parent,children,DocStyleChange::Del,tokenName);
1405 case HTML_UNDERLINE:
1406 if (!context.token->endTag)
1408 handleStyleEnter(parent,children,DocStyleChange::Underline,tokenName,&context.token->attribs);
1412 handleStyleLeave(parent,children,DocStyleChange::Underline,tokenName);
1416 if (!context.token->endTag)
1418 handleStyleEnter(parent,children,DocStyleChange::Ins,tokenName,&context.token->attribs);
1422 handleStyleLeave(parent,children,DocStyleChange::Ins,tokenName);
1427 if (!context.token->endTag)
1429 handleStyleEnter(parent,children,DocStyleChange::Code,tokenName,&context.token->attribs);
1433 handleStyleLeave(parent,children,DocStyleChange::Code,tokenName);
1437 if (!context.token->endTag)
1439 handleStyleEnter(parent,children,DocStyleChange::Italic,tokenName,&context.token->attribs);
1443 handleStyleLeave(parent,children,DocStyleChange::Italic,tokenName);
1447 if (!context.token->endTag)
1449 handleStyleEnter(parent,children,DocStyleChange::Subscript,tokenName,&context.token->attribs);
1453 handleStyleLeave(parent,children,DocStyleChange::Subscript,tokenName);
1457 if (!context.token->endTag)
1459 handleStyleEnter(parent,children,DocStyleChange::Superscript,tokenName,&context.token->attribs);
1463 handleStyleLeave(parent,children,DocStyleChange::Superscript,tokenName);
1467 if (!context.token->endTag)
1469 handleStyleEnter(parent,children,DocStyleChange::Center,tokenName,&context.token->attribs);
1473 handleStyleLeave(parent,children,DocStyleChange::Center,tokenName);
1477 if (!context.token->endTag)
1479 handleStyleEnter(parent,children,DocStyleChange::Small,tokenName,&context.token->attribs);
1483 handleStyleLeave(parent,children,DocStyleChange::Small,tokenName);
1487 if (!context.token->endTag)
1489 handleStyleEnter(parent,children,DocStyleChange::Cite,tokenName,&context.token->attribs);
1493 handleStyleLeave(parent,children,DocStyleChange::Cite,tokenName);
1497 if (!context.token->endTag)
1498 handleImg(parent,children,context.token->attribs);
1508 HtmlEntityMapper::SymType s = DocSymbol::decodeSymbol(tokenName);
1509 if (s!=HtmlEntityMapper::Sym_Unknown)
1511 children.append<DocSymbol>(this,parent,s);
1522 if (insidePRE(parent) || !children.empty())
1524 children.append<DocWhiteSpace>(this,parent,context.token->chars);
1530 handleLinkedWord(parent,children);
1538 children.append<DocWord>(this,parent,context.token->name);
1544 if (context.insideHtmlLink)
1546 children.append<DocWord>(this,parent,context.token->name);
1550 children.append<DocURL>(this,parent,context.token->name,context.token->isEMailAddr);
1559 //---------------------------------------------------------------------------
1561 void DocParser::handleImg(DocNodeVariant *parent, DocNodeList &children,const HtmlAttribList &tagHtmlAttribs)
1566 for (const auto &opt : tagHtmlAttribs)
1568 AUTO_TRACE_ADD("option name={} value='{}'",opt.name,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!");
1590 //---------------------------------------------------------------------------
1592 int DocParser::internalValidatingParseDoc(DocNodeVariant *parent,DocNodeList &children,
1593 const QCString &doc)
1596 int retval = RetVal_OK;
1598 if (doc.isEmpty()) return retval;
1600 tokenizer.init(doc.data(),context.fileName,context.markdownSupport,context.insideHtmlLink);
1602 // first parse any number of paragraphs
1604 DocPara *lastPar=!children.empty() ? std::get_if<DocPara>(&children.back()): 0;
1606 { // last child item was a paragraph
1611 children.append<DocPara>(this,parent);
1612 DocPara *par = children.get_last<DocPara>();
1613 if (isFirst) { par->markFirst(); isFirst=FALSE; }
1614 retval=par->parse();
1615 if (!par->isEmpty())
1617 if (lastPar) lastPar->markLast(FALSE);
1622 children.pop_back();
1624 } while (retval==TK_NEWPARA);
1625 if (lastPar) lastPar->markLast();
1627 AUTO_TRACE_EXIT("isFirst={} isLast={}",lastPar?lastPar->isFirst():-1,lastPar?lastPar->isLast():-1);
1631 //---------------------------------------------------------------------------
1633 void DocParser::readTextFileByName(const QCString &file,QCString &text)
1635 AUTO_TRACE("file={} text={}",file,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, uint32_t &j, size_t 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 uint32_t isCopyBriefOrDetailsCmd(const char *data, uint32_t i,size_t 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 uint32_t isVerbatimSection(const char *data,uint32_t i,size_t 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 uint32_t skipToEndMarker(const char *data,uint32_t i,size_t 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 : static_cast<uint32_t>(len);
1797 QCString DocParser::processCopyDoc(const char *data,size_t &len)
1799 AUTO_TRACE("data={} len={}",Trace::trunc(data),len);
1802 int lineNr = tokenizer.getLineNr();
1806 if (c=='@' || c=='\\') // look for a command
1809 uint32_t 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);
1827 auto addDocs = [&](const QCString &file_,int line_,const QCString &doc_)
1829 buf.addStr(" \\ilinebr\\ifile \""+file_+"\" ");
1830 buf.addStr("\\iline "+QCString().setNum(line_)+" ");
1831 size_t len_ = doc_.length();
1832 buf.addStr(processCopyDoc(doc_.data(),len_));
1836 addDocs(def->briefFile(),def->briefLine(),brief);
1840 addDocs(def->docFile(),def->docLine(),doc);
1841 if (def->definitionType()==Definition::TypeMember)
1843 const MemberDef *md = toMemberDef(def);
1844 const ArgumentList &docArgList = md->templateMaster() ?
1845 md->templateMaster()->argumentList() :
1847 buf.addStr(inlineArgListToDoc(docArgList));
1850 context.copyStack.pop_back();
1851 buf.addStr(" \\ilinebr\\ifile \""+context.fileName+"\" ");
1852 buf.addStr("\\iline "+QCString().setNum(lineNr)+" ");
1856 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1857 "Found recursive @copy%s or @copydoc relation for argument '%s'.",
1858 isBrief?"brief":"details",qPrint(id));
1863 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1864 "@copy%s or @copydoc target '%s' not found", isBrief?"brief":"details",
1867 // skip over command
1873 uint32_t k = isVerbatimSection(data,i,len,endMarker);
1876 uint32_t orgPos = i;
1877 i=skipToEndMarker(data,k,len,endMarker);
1878 buf.addStr(data+orgPos,i-orgPos);
1879 // TODO: adjust lineNr
1888 else // not a command, just copy
1892 lineNr += (c=='\n') ? 1 : 0;
1895 len = static_cast<uint32_t>(buf.getPos());
1897 AUTO_TRACE_EXIT("result={}",Trace::trunc(buf.get()));
1902 //---------------------------------------------------------------------------
1904 IDocNodeASTPtr validatingParseDoc(IDocParser &parserIntf,
1905 const QCString &fileName,int startLine,
1906 const Definition *ctx,const MemberDef *md,
1907 const QCString &input,bool indexWords,
1908 bool isExample, const QCString &exampleName,
1909 bool singleLine, bool linkFromIndex,
1910 bool markdownSupport)
1912 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
1914 if (parser==0) return 0;
1915 //printf("validatingParseDoc(%s,%s)=[%s]\n",ctx?qPrint(ctx->name()):"<none>",
1916 // md?qPrint(md->name()):"<none>",
1918 //printf("========== validating %s at line %d\n",qPrint(fileName),startLine);
1919 //printf("---------------- input --------------------\n%s\n----------- end input -------------------\n",qPrint(input));
1921 // set initial token
1922 parser->context.token = parser->tokenizer.resetToken();
1924 if (ctx && ctx!=Doxygen::globalScope &&
1925 (ctx->definitionType()==Definition::TypeClass ||
1926 ctx->definitionType()==Definition::TypeNamespace
1930 parser->context.context = ctx->qualifiedName();
1932 else if (ctx && ctx->definitionType()==Definition::TypePage)
1934 const Definition *scope = (toPageDef(ctx))->getPageScope();
1935 if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1937 else if (ctx && ctx->definitionType()==Definition::TypeGroup)
1939 const Definition *scope = (toGroupDef(ctx))->getGroupScope();
1940 if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1944 parser->context.context = "";
1946 parser->context.scope = ctx;
1948 if (indexWords && Doxygen::searchIndex)
1952 parser->context.searchUrl=md->getOutputFileBase();
1953 Doxygen::searchIndex->setCurrentDoc(md,md->anchor(),false);
1957 parser->context.searchUrl=ctx->getOutputFileBase();
1958 Doxygen::searchIndex->setCurrentDoc(ctx,ctx->anchor(),false);
1963 parser->context.searchUrl="";
1966 parser->context.fileName = fileName;
1967 parser->context.relPath = (!linkFromIndex && ctx) ?
1968 QCString(relativePathToRoot(ctx->getOutputFileBase())) :
1970 //printf("ctx->name=%s relPath=%s\n",qPrint(ctx->name()),qPrint(parser->context.relPath));
1971 parser->context.memberDef = md;
1972 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
1973 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
1974 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
1975 parser->context.inSeeBlock = FALSE;
1976 parser->context.xmlComment = FALSE;
1977 parser->context.insideHtmlLink = FALSE;
1978 parser->context.includeFileText = "";
1979 parser->context.includeFileOffset = 0;
1980 parser->context.includeFileLength = 0;
1981 parser->context.isExample = isExample;
1982 parser->context.exampleName = exampleName;
1983 parser->context.hasParamCommand = FALSE;
1984 parser->context.hasReturnCommand = FALSE;
1985 parser->context.retvalsFound.clear();
1986 parser->context.paramsFound.clear();
1987 parser->context.markdownSupport = markdownSupport;
1989 //printf("Starting comment block at %s:%d\n",qPrint(parser->context.fileName),startLine);
1990 parser->tokenizer.setLineNr(startLine);
1991 size_t ioLen = input.length();
1992 QCString inpStr = parser->processCopyDoc(input.data(),ioLen);
1993 if (inpStr.isEmpty() || inpStr.at(inpStr.length()-1)!='\n')
1997 //printf("processCopyDoc(in='%s' out='%s')\n",input,qPrint(inpStr));
1998 parser->tokenizer.init(inpStr.data(),parser->context.fileName,
1999 parser->context.markdownSupport,parser->context.insideHtmlLink);
2001 // build abstract syntax tree
2002 auto ast = std::make_unique<DocNodeAST>(DocRoot(parser,md!=0,singleLine));
2003 std::get<DocRoot>(ast->root).parse();
2005 if (Debug::isFlagSet(Debug::PrintTree))
2007 // pretty print the result
2008 std::visit(PrintDocVisitor{},ast->root);
2011 if (md && md->isFunction())
2013 parser->checkUnOrMultipleDocumentedParams();
2015 if (parser->context.memberDef) parser->context.memberDef->detectUndocumentedParams(parser->context.hasParamCommand,parser->context.hasReturnCommand);
2018 parser->tokenizer.resetToken();
2020 //printf(">>>>>> end validatingParseDoc(%s,%s)\n",ctx?qPrint(ctx->name()):"<none>",
2021 // md?qPrint(md->name()):"<none>");
2026 IDocNodeASTPtr validatingParseText(IDocParser &parserIntf,const QCString &input)
2028 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2030 if (parser==0) return 0;
2032 // set initial token
2033 parser->context.token = parser->tokenizer.resetToken();
2035 //printf("------------ input ---------\n%s\n"
2036 // "------------ end input -----\n",input);
2037 //parser->context.token = new TokenInfo;
2038 parser->context.context = "";
2039 parser->context.fileName = "<parseText>";
2040 parser->context.relPath = "";
2041 parser->context.memberDef = 0;
2042 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
2043 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
2044 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
2045 parser->context.inSeeBlock = FALSE;
2046 parser->context.xmlComment = FALSE;
2047 parser->context.insideHtmlLink = FALSE;
2048 parser->context.includeFileText = "";
2049 parser->context.includeFileOffset = 0;
2050 parser->context.includeFileLength = 0;
2051 parser->context.isExample = FALSE;
2052 parser->context.exampleName = "";
2053 parser->context.hasParamCommand = FALSE;
2054 parser->context.hasReturnCommand = FALSE;
2055 parser->context.retvalsFound.clear();
2056 parser->context.paramsFound.clear();
2057 parser->context.searchUrl="";
2058 parser->context.markdownSupport = Config_getBool(MARKDOWN_SUPPORT);
2061 auto ast = std::make_unique<DocNodeAST>(DocText(parser));
2063 if (!input.isEmpty())
2065 parser->tokenizer.setLineNr(1);
2066 parser->tokenizer.init(input.data(),parser->context.fileName,
2067 parser->context.markdownSupport,parser->context.insideHtmlLink);
2069 // build abstract syntax tree
2070 std::get<DocText>(ast->root).parse();
2072 if (Debug::isFlagSet(Debug::PrintTree))
2074 // pretty print the result
2075 std::visit(PrintDocVisitor{},ast->root);
2082 IDocNodeASTPtr createRef(IDocParser &parserIntf,const QCString &target,const QCString &context)
2084 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2086 if (parser==0) return 0;
2087 return std::make_unique<DocNodeAST>(DocRef(parser,0,target,context));
2090 void docFindSections(const QCString &input,
2091 const Definition *d,
2092 const QCString &fileName)
2095 parser.tokenizer.findSections(input,d,fileName);