88c97f0f869fac967ba262ca3a0d6e8a2f895d8e
[platform/upstream/doxygen.git] / src / docparser.cpp
1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2022 by Dimitri van Heesch.
4  *
5  * Permission to use, copy, modify, and distribute this software and its
6  * documentation under the terms of the GNU General Public License is hereby
7  * granted. No representations are made about the suitability of this software
8  * for any purpose. It is provided "as is" without express or implied warranty.
9  * See the GNU General Public License for more details.
10  *
11  * Documents produced by Doxygen are derivative works derived from the
12  * input used in their production; they are not affected by this license.
13  *
14  */
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <cassert>
19
20 #include <ctype.h>
21
22 #include "classlist.h"
23 #include "cmdmapper.h"
24 #include "config.h"
25 #include "debug.h"
26 #include "dir.h"
27 #include "docparser.h"
28 #include "docparser_p.h"
29 #include "doxygen.h"
30 #include "filedef.h"
31 #include "fileinfo.h"
32 #include "groupdef.h"
33 #include "namespacedef.h"
34 #include "message.h"
35 #include "pagedef.h"
36 #include "portable.h"
37 #include "printdocvisitor.h"
38 #include "util.h"
39 #include "indexlist.h"
40
41 // debug off
42 #define DBG(x) do {} while(0)
43
44 // debug to stdout
45 //#define DBG(x) printf x
46
47 // debug to stderr
48 //#define myprintf(...) fprintf(stderr,__VA_ARGS__)
49 //#define DBG(x) myprintf x
50
51
52 //---------------------------------------------------------------------------
53
54 IDocParserPtr createDocParser()
55 {
56   return std::make_unique<DocParser>();
57 }
58
59 //---------------------------------------------------------------------------
60 DocParser::~DocParser()
61 {
62   try
63   {
64     searchData.transfer();
65   }
66   catch(...)
67   {
68     err("Unexpected exception caught in DocParser\n");
69   }
70 }
71
72 void DocParser::pushContext()
73 {
74   //QCString indent;
75   //indent.fill(' ',contextStack.size()*2+2);
76   //printf("%sdocParserPushContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
77
78   tokenizer.pushContext();
79   contextStack.push(DocParserContext());
80   auto &ctx = contextStack.top();
81   ctx = context;
82   ctx.lineNo = tokenizer.getLineNr();
83   context.token = tokenizer.newToken();
84 }
85
86 void DocParser::popContext()
87 {
88   auto &ctx = contextStack.top();
89   context = ctx;
90   tokenizer.setLineNr(ctx.lineNo);
91   context.token = ctx.token;
92   tokenizer.replaceToken(context.token);
93   contextStack.pop();
94   tokenizer.popContext();
95
96   //QCString indent;
97   //indent.fill(' ',contextStack.size()*2+2);
98   //printf("%sdocParserPopContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
99 }
100
101 //---------------------------------------------------------------------------
102
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
105  * parameter).
106  */
107 QCString DocParser::findAndCopyImage(const QCString &fileName, DocImage::Type type, bool doWarn)
108 {
109   QCString result;
110   bool ambig;
111   FileDef *fd = findFileDef(Doxygen::imageNameLinkedMap,fileName,ambig);
112   //printf("Search for %s\n",fileName);
113   if (fd)
114   {
115     if (ambig & doWarn)
116     {
117       QCString text;
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));
122     }
123
124     QCString inputFile = fd->absFilePath();
125     FileInfo infi(inputFile.str());
126     if (infi.exists())
127     {
128       result = fileName;
129       int i;
130       if ((i=result.findRev('/'))!=-1 || (i=result.findRev('\\'))!=-1)
131       {
132         result = result.right(static_cast<int>(result.length())-i-1);
133       }
134       //printf("fileName=%s result=%s\n",fileName,qPrint(result));
135       QCString outputDir;
136       switch(type)
137       {
138         case DocImage::Html:
139           if (!Config_getBool(GENERATE_HTML)) return result;
140           outputDir = Config_getString(HTML_OUTPUT);
141           break;
142         case DocImage::Latex:
143           if (!Config_getBool(GENERATE_LATEX)) return result;
144           outputDir = Config_getString(LATEX_OUTPUT);
145           break;
146         case DocImage::DocBook:
147           if (!Config_getBool(GENERATE_DOCBOOK)) return result;
148           outputDir = Config_getString(DOCBOOK_OUTPUT);
149           break;
150         case DocImage::Rtf:
151           if (!Config_getBool(GENERATE_RTF)) return result;
152           outputDir = Config_getString(RTF_OUTPUT);
153           break;
154         case DocImage::Xml:
155           if (!Config_getBool(GENERATE_XML)) return result;
156           outputDir = Config_getString(XML_OUTPUT);
157           break;
158       }
159       QCString outputFile = outputDir+"/"+result;
160       FileInfo outfi(outputFile.str());
161       if (outfi.isSymLink())
162       {
163         Dir().remove(outputFile.str());
164         warn_doc_error(context.fileName,tokenizer.getLineNr(),
165             "destination of image %s is a symlink, replacing with image",
166             qPrint(outputFile));
167       }
168       if (outputFile!=inputFile) // prevent copying to ourself
169       {
170         if (copyFile(inputFile,outputFile) && type==DocImage::Html)
171         {
172           Doxygen::indexList->addImageFile(result);
173         }
174       }
175     }
176     else
177     {
178       warn_doc_error(context.fileName,tokenizer.getLineNr(),
179           "could not open image %s",qPrint(fileName));
180     }
181
182     if (type==DocImage::Latex && Config_getBool(USE_PDFLATEX) &&
183         fd->name().endsWith(".eps")
184        )
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)
194       {
195         err("Problems running epstopdf. Check your TeX installation!\n");
196       }
197       Portable::sysTimerStop();
198       return baseName;
199     }
200   }
201   else
202   {
203     result=fileName;
204     if (!result.startsWith("http:") && !result.startsWith("https:") && doWarn)
205     {
206       warn_doc_error(context.fileName,tokenizer.getLineNr(),
207            "image file %s is not found in IMAGE_PATH: "
208            "assuming external image.",qPrint(fileName)
209           );
210     }
211   }
212   return result;
213 }
214
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).
220  */
221 void DocParser::checkArgumentName()
222 {
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
232
233   static const reg::Ex re(R"(\$?\w+\.*)");
234   reg::Iterator it(name,re);
235   reg::Iterator end;
236   for (; it!=end ; ++it)
237   {
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));
242     bool found=FALSE;
243     for (const Argument &a : al)
244     {
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);
250       if (aName==argName)
251       {
252         context.paramsFound.insert(aName.str());
253         found=TRUE;
254         break;
255       }
256     }
257     if (!found)
258     {
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
267       {
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();
273       }
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));
280     }
281   }
282 }
283 /*! Collects the return values found with \@retval command
284  *  in a global list g_parserContext.retvalsFound.
285  */
286 void DocParser::checkRetvalName()
287 {
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
292   {
293      warn_doc_error(context.memberDef->getDefFileName(),
294                     context.memberDef->getDefLine(),
295                     "%s",
296                     qPrint("return value '" + name + "' of " +
297                     QCString(context.memberDef->qualifiedName()) +
298                     " has multiple documentation sections"));
299   }
300   context.retvalsFound.insert(name.str());
301 }
302
303 /*! Checks if the parameters that have been specified using \@param are
304  *  indeed all parameters and that a parameter does not have multiple
305  *  \@param blocks.
306  *  Must be called after checkArgumentName() has been called for each
307  *  argument.
308  */
309 void DocParser::checkUnOrMultipleDocumentedParams()
310 {
311   if (context.memberDef && context.hasParamCommand)
312   {
313     const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
314       context.memberDef->argumentList() :
315       context.memberDef->declArgumentList();
316     SrcLangExt lang = context.memberDef->getLanguage();
317     if (!al.empty())
318     {
319       ArgumentList undocParams;
320       for (const Argument &a: al)
321       {
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"))
328         {
329           // allow undocumented self / cls parameter for Python
330         }
331         else if (!argName.isEmpty())
332         {
333           size_t count = context.paramsFound.count(argName.str());
334           if (count==0 && a.docs.isEmpty())
335           {
336             undocParams.push_back(a);
337           }
338           else if (count>1 && Config_getBool(WARN_IF_DOC_ERROR))
339           {
340             warn_doc_error(context.memberDef->docFile(),
341                            context.memberDef->docLine(),
342                            "%s",
343                            qPrint("argument '" + aName +
344                            "' from the argument list of " +
345                            QCString(context.memberDef->qualifiedName()) +
346                            " has multiple @param documentation sections"));
347           }
348         }
349       }
350       if (!undocParams.empty() && Config_getBool(WARN_IF_INCOMPLETE_DOC))
351       {
352         bool first=TRUE;
353         QCString errMsg = "The following parameter";
354         if (undocParams.size()>1) errMsg+="s";
355         errMsg+=" of "+
356             QCString(context.memberDef->qualifiedName()) +
357             QCString(argListToString(al)) +
358             (undocParams.size()>1 ? " are" : " is") + " not documented:\n";
359         for (const Argument &a : undocParams)
360         {
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";
365           first=FALSE;
366           errMsg+="  parameter '"+argName+"'";
367         }
368         warn_incomplete_doc(context.memberDef->docFile(),
369                             context.memberDef->docLine(),
370                             "%s",
371                             qPrint(substitute(errMsg,"%","%%")));
372       }
373     }
374     else
375     {
376       if (!context.paramsFound.size() && Config_getBool(WARN_IF_DOC_ERROR))
377       {
378         warn_doc_error(context.memberDef->docFile(),
379                        context.memberDef->docLine(),
380                        "%s",
381                        qPrint(context.memberDef->qualifiedName() +
382                               " has @param documentation sections but no arguments"));
383       }
384     }
385   }
386 }
387
388
389 //---------------------------------------------------------------------------
390
391 //---------------------------------------------------------------------------
392
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
397  *  put in pDef.
398  *  @retval TRUE if name was found.
399  *  @retval FALSE if name was not found.
400  */
401 bool DocParser::findDocsForMemberOrCompound(const QCString &commandName,
402                                             QCString *pDoc,
403                                             QCString *pBrief,
404                                             const Definition **pDef)
405 {
406   //printf("findDocsForMemberOrCompound(%s)\n",commandName);
407   *pDoc="";
408   *pBrief="";
409   *pDef=0;
410   QCString cmdArg=commandName;
411   if (cmdArg.isEmpty()) return FALSE;
412
413   const FileDef      *fd=0;
414   const GroupDef     *gd=0;
415   const PageDef      *pd=0;
416   gd = Doxygen::groupLinkedMap->find(cmdArg);
417   if (gd) // group
418   {
419     *pDoc=gd->documentation();
420     *pBrief=gd->briefDescription();
421     *pDef=gd;
422     return TRUE;
423   }
424   pd = Doxygen::pageLinkedMap->find(cmdArg);
425   if (pd) // page
426   {
427     *pDoc=pd->documentation();
428     *pBrief=pd->briefDescription();
429     *pDef=pd;
430     return TRUE;
431   }
432   bool ambig;
433   fd = findFileDef(Doxygen::inputNameLinkedMap,cmdArg,ambig);
434   if (fd && !ambig) // file
435   {
436     *pDoc=fd->documentation();
437     *pBrief=fd->briefDescription();
438     *pDef=fd;
439     return TRUE;
440   }
441
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);
446   if (extractAnonNs &&
447       cmdArg.startsWith("anonymous_namespace{")
448       )
449   {
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;
455   }
456   else
457   {
458     cmdArg = substitute(cmdArg,".","::");
459   }
460
461   int l=static_cast<int>(cmdArg.length());
462
463   int funcStart=cmdArg.find('(');
464   if (funcStart==-1)
465   {
466     funcStart=l;
467   }
468   else
469   {
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)
475     {
476       if (leftParen<secondParen)
477       {
478         funcStart=secondParen;
479       }
480     }
481   }
482
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
491       name,
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));
495   if (found && md)
496   {
497     *pDoc=md->documentation();
498     *pBrief=md->briefDescription();
499     *pDef=md;
500     return TRUE;
501   }
502
503
504   int scopeOffset=static_cast<int>(context.context.length());
505   do // for each scope
506   {
507     QCString fullName=cmdArg;
508     if (scopeOffset>0)
509     {
510       fullName.prepend(context.context.left(scopeOffset)+"::");
511     }
512     //printf("Trying fullName='%s'\n",qPrint(fullName));
513
514     // try class, namespace, group, page, file reference
515     cd = Doxygen::classLinkedMap->find(fullName);
516     if (cd) // class
517     {
518       *pDoc=cd->documentation();
519       *pBrief=cd->briefDescription();
520       *pDef=cd;
521       return TRUE;
522     }
523     nd = Doxygen::namespaceLinkedMap->find(fullName);
524     if (nd) // namespace
525     {
526       *pDoc=nd->documentation();
527       *pBrief=nd->briefDescription();
528       *pDef=nd;
529       return TRUE;
530     }
531     if (scopeOffset==0)
532     {
533       scopeOffset=-1;
534     }
535     else
536     {
537       scopeOffset = context.context.findRev("::",scopeOffset-1);
538       if (scopeOffset==-1) scopeOffset=0;
539     }
540   } while (scopeOffset>=0);
541
542
543   return FALSE;
544 }
545
546 //---------------------------------------------------------------------------
547 void DocParser::errorHandleDefaultToken(DocNodeVariant *parent,int tok,
548                                         DocNodeList &children,const QCString &txt)
549 {
550   const char *cmd_start = "\\";
551   switch (tok)
552   {
553     case TK_COMMAND_AT:
554       cmd_start = "@";
555       // fall through
556     case TK_COMMAND_BS:
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));
560       break;
561     case TK_SYMBOL:
562       warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported symbol %s found as part of a %s",
563            qPrint(context.token->name), qPrint(txt));
564       break;
565     default:
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));
569       break;
570   }
571 }
572
573 //---------------------------------------------------------------------------
574
575 int DocParser::handleStyleArgument(DocNodeVariant *parent,DocNodeList &children,const QCString &cmdName)
576 {
577   DBG(("handleStyleArgument(%s)\n",qPrint(cmdName)));
578   QCString saveCmdName = cmdName;
579   int tok=tokenizer.lex();
580   if (tok!=TK_WHITESPACE)
581   {
582     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
583         qPrint(saveCmdName));
584     return tok;
585   }
586   while ((tok=tokenizer.lex()) &&
587           tok!=TK_WHITESPACE &&
588           tok!=TK_NEWPARA &&
589           tok!=TK_LISTITEM &&
590           tok!=TK_ENDLIST
591         )
592   {
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))
596     {
597       // special character that ends the markup command
598       return tok;
599     }
600     if (!defaultHandleToken(parent,tok,children))
601     {
602       switch (tok)
603       {
604         case TK_HTMLTAG:
605           if (insideLI(parent) && Mappers::htmlTagMapper->map(context.token->name) && context.token->endTag)
606           { // ignore </li> as the end of a style command
607             continue;
608           }
609           DBG(("handleStyleArgument(%s) end tok=%s\n",qPrint(saveCmdName), DocTokenizer::tokToString(tok)));
610           return tok;
611           break;
612         default:
613           errorHandleDefaultToken(parent,tok,children,"\\" + saveCmdName + " command");
614           break;
615       }
616       break;
617     }
618   }
619   DBG(("handleStyleArgument(%s) end tok=%s\n",qPrint(saveCmdName), DocTokenizer::tokToString(tok)));
620   return (tok==TK_NEWPARA || tok==TK_LISTITEM || tok==TK_ENDLIST
621          ) ? tok : RetVal_OK;
622 }
623
624 /*! Called when a style change starts. For instance a \<b\> command is
625  *  encountered.
626  */
627 void DocParser::handleStyleEnter(DocNodeVariant *parent,DocNodeList &children,
628           DocStyleChange::Style s,const QCString &tagName,const HtmlAttribList *attribs)
629 {
630   DBG(("HandleStyleEnter\n"));
631   children.append<DocStyleChange>(this,parent,context.nodeStack.size(),s,tagName,TRUE,attribs);
632   context.styleStack.push(&children.back());
633 }
634
635 /*! Called when a style change ends. For instance a \</b\> command is
636  *  encountered.
637  */
638 void DocParser::handleStyleLeave(DocNodeVariant *parent,DocNodeList &children,
639          DocStyleChange::Style s,const QCString &tagName)
640 {
641   DBG(("HandleStyleLeave\n"));
642   QCString tagNameLower = QCString(tagName).lower();
643
644   auto topStyleChange = [](const DocStyleChangeStack &stack) -> const DocStyleChange &
645   {
646     return std::get<DocStyleChange>(*stack.top());
647   };
648
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
653      )
654   {
655     if (context.styleStack.empty())
656     {
657       warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag without matching <%s>",
658           qPrint(tagName),qPrint(tagName));
659     }
660     else if (topStyleChange(context.styleStack).tagName()!=tagNameLower)
661     {
662       warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
663           qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
664     }
665     else if (topStyleChange(context.styleStack).style()!=s)
666     {
667       warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
668           qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
669     }
670     else
671     {
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());
674     }
675   }
676   else // end the section
677   {
678     children.append<DocStyleChange>(
679           this,parent,context.nodeStack.size(),s,
680           topStyleChange(context.styleStack).tagName(),FALSE);
681     context.styleStack.pop();
682   }
683 }
684
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.
688  */
689 void DocParser::handlePendingStyleCommands(DocNodeVariant *parent,DocNodeList &children)
690 {
691   if (!context.styleStack.empty())
692   {
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;
701     }
702   }
703 }
704
705 void DocParser::handleInitialStyleCommands(DocNodeVariant *parent,DocNodeList &children)
706 {
707   while (!context.initialStyleStack.empty())
708   {
709     const DocStyleChange &sc = std::get<DocStyleChange>(*context.initialStyleStack.top());
710     handleStyleEnter(parent,children,sc.style(),sc.tagName(),&sc.attribs());
711     context.initialStyleStack.pop();
712   }
713 }
714
715 int DocParser::handleAHref(DocNodeVariant *parent,DocNodeList &children,
716                            const HtmlAttribList &tagHtmlAttribs)
717 {
718   uint index=0;
719   int retval = RetVal_OK;
720   for (const auto &opt : tagHtmlAttribs)
721   {
722     if (opt.name=="name" || opt.name=="id") // <a name=label> or <a id=label> tag
723     {
724       if (!opt.value.isEmpty())
725       {
726         children.append<DocAnchor>(this,parent,opt.value,TRUE);
727         break; // stop looking for other tag attribs
728       }
729       else
730       {
731         warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <a> tag with name option but without value!");
732       }
733     }
734     else if (opt.name=="href") // <a href=url>..</a> tag
735     {
736       // copy attributes
737       HtmlAttribList attrList = tagHtmlAttribs;
738       // and remove the href attribute
739       attrList.erase(attrList.begin()+index);
740       QCString relPath;
741       if (opt.value.at(0) != '#') relPath = context.relPath;
742       auto vDocHRef = children.append<DocHRef>(this, parent, attrList,
743                                                opt.value, relPath,
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;
749       break;
750     }
751     else // unsupported option for tag a
752     {
753     }
754     ++index;
755   }
756   return retval;
757 }
758
759 void DocParser::handleUnclosedStyleCommands()
760 {
761   if (!context.initialStyleStack.empty())
762   {
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));
769   }
770 }
771
772 void DocParser::handleLinkedWord(DocNodeVariant *parent,DocNodeList &children,bool ignoreAutoLinkFlag)
773 {
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
777   {
778     children.append<DocWord>(this,parent,name);
779     return;
780   }
781
782   // ------- try to turn the word 'name' into a link
783
784   const Definition *compound=0;
785   const MemberDef  *member=0;
786   uint len = context.token->name.length();
787   ClassDef *cd=0;
788   bool ambig;
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))
795       )
796      )
797   {
798     //printf("resolveRef %s = %p (linkable?=%d)\n",qPrint(context.token->name),member,member ? member->isLinkable() : FALSE);
799     if (member && member->isLinkable()) // member link
800     {
801       if (member->isObjCMethod())
802       {
803         bool localLink = context.memberDef ? member->getClassDef()==context.memberDef->getClassDef() : FALSE;
804         name = member->objCMethodName(localLink,context.inSeeBlock);
805       }
806       children.append<DocLinkedWord>(
807             this,parent,name,
808             member->getReference(),
809             member->getOutputFileBase(),
810             member->anchor(),
811             member->briefDescriptionAsTooltip());
812     }
813     else if (compound->isLinkable()) // compound link
814     {
815       QCString anchor = compound->anchor();
816       if (compound->definitionType()==Definition::TypeFile)
817       {
818         name=context.token->name;
819       }
820       else if (compound->definitionType()==Definition::TypeGroup)
821       {
822         name=toGroupDef(compound)->groupTitle();
823       }
824       children.append<DocLinkedWord>(
825             this,parent,name,
826             compound->getReference(),
827             compound->getOutputFileBase(),
828             anchor,
829             compound->briefDescriptionAsTooltip());
830     }
831     else if (compound->definitionType()==Definition::TypeFile &&
832              (toFileDef(compound))->generateSourceFile()
833             ) // undocumented file that has source code we can link to
834     {
835       children.append<DocLinkedWord>(
836              this,parent,context.token->name,
837              compound->getReference(),
838              compound->getSourceFileBase(),
839              "",
840              compound->briefDescriptionAsTooltip());
841     }
842     else // not linkable
843     {
844       children.append<DocWord>(this,parent,name);
845     }
846   }
847   else if (!context.insideHtmlLink && len>1 && context.token->name.at(len-1)==':')
848   {
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,":");
854   }
855   else if (!context.insideHtmlLink && (cd=getClass(context.token->name+"-p")))
856   {
857     // special case 2, where the token name is not a class, but could
858     // be a Obj-C protocol
859     children.append<DocLinkedWord>(
860           this,parent,name,
861           cd->getReference(),
862           cd->getOutputFileBase(),
863           cd->anchor(),
864           cd->briefDescriptionAsTooltip());
865   }
866   else // normal non-linkable word
867   {
868     if (context.token->name.startsWith("#") || context.token->name.startsWith("::"))
869     {
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);
872     }
873     else
874     {
875       children.append<DocWord>(this,parent,name);
876     }
877   }
878 }
879
880 void DocParser::handleParameterType(DocNodeVariant *parent,DocNodeList &children,const QCString &paramTypes)
881 {
882   QCString name = context.token->name; // save token name
883   QCString name1;
884   int p=0,i,ii;
885   while ((i=paramTypes.find('|',p))!=-1)
886   {
887     name1 = paramTypes.mid(p,i-p);
888     ii=name1.find('[');
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
892     p=i+1;
893     children.append<DocSeparator>(this,parent,"|");
894   }
895   name1 = paramTypes.mid(p);
896   ii=name1.find('[');
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
901 }
902
903 void DocParser::handleInternalRef(DocNodeVariant *parent,DocNodeList &children)
904 {
905   //printf("CMD_INTERNALREF\n");
906   int tok=tokenizer.lex();
907   QCString tokenName = context.token->name;
908   if (tok!=TK_WHITESPACE)
909   {
910     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
911         qPrint(tokenName));
912     return;
913   }
914   tokenizer.setStateInternalRef();
915   tok=tokenizer.lex(); // get the reference id
916   if (tok!=TK_WORD && tok!=TK_LNKWORD)
917   {
918     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
919         DocTokenizer::tokToString(tok),qPrint(tokenName));
920     return;
921   }
922   auto vDocInternalRef = children.append<DocInternalRef>(this,parent,context.token->name);
923   children.get_last<DocInternalRef>()->parse(vDocInternalRef);
924 }
925
926 void DocParser::handleAnchor(DocNodeVariant *parent,DocNodeList &children)
927 {
928   int tok=tokenizer.lex();
929   if (tok!=TK_WHITESPACE)
930   {
931     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
932         qPrint(context.token->name));
933     return;
934   }
935   tokenizer.setStateAnchor();
936   tok=tokenizer.lex();
937   if (tok==0)
938   {
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));
941     return;
942   }
943   else if (tok!=TK_WORD && tok!=TK_LNKWORD)
944   {
945     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
946         DocTokenizer::tokToString(tok),qPrint(context.token->name));
947     return;
948   }
949   tokenizer.setStatePara();
950   children.append<DocAnchor>(this,parent,context.token->name,FALSE);
951 }
952
953
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
962  */
963 void DocParser::defaultHandleTitleAndSize(const int cmd, DocNodeVariant *parent, DocNodeList &children, QCString &width,QCString &height)
964 {
965   auto ns = AutoNodeStack(this,parent);
966
967   // parse title
968   tokenizer.setStateTitle();
969   int tok;
970   while ((tok=tokenizer.lex()))
971   {
972     if (tok==TK_WORD && (context.token->name=="width=" || context.token->name=="height="))
973     {
974       // special case: no title, but we do have a size indicator
975       break;
976     }
977     else if (tok==TK_HTMLTAG)
978     {
979       tokenizer.unputString(context.token->name);
980       break;
981     }
982     if (!defaultHandleToken(parent,tok,children))
983     {
984       errorHandleDefaultToken(parent,tok,children,Mappers::cmdMapper->find(cmd));
985     }
986   }
987   // parse size attributes
988   if (tok == 0)
989   {
990     tok=tokenizer.lex();
991   }
992   while (tok==TK_WHITESPACE || tok==TK_WORD) // there are values following the title
993   {
994     if (tok==TK_WORD)
995     {
996       if (context.token->name=="width=" || context.token->name=="height=")
997       {
998         tokenizer.setStateTitleAttrValue();
999         context.token->name = context.token->name.left(context.token->name.length()-1);
1000       }
1001
1002       if (context.token->name=="width")
1003       {
1004         width = context.token->chars;
1005       }
1006       else if (context.token->name=="height")
1007       {
1008         height = context.token->chars;
1009       }
1010       else
1011       {
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)));
1015         break;
1016       }
1017     }
1018
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)
1023     {
1024       tokenizer.unputString(context.token->name);
1025       tokenizer.unputString(tok==TK_COMMAND_AT ? "@" : "\\");
1026     }
1027     else if (tok==TK_SYMBOL || tok==TK_HTMLTAG)
1028     {
1029       tokenizer.unputString(context.token->name);
1030     }
1031   }
1032   tokenizer.setStatePara();
1033
1034   handlePendingStyleCommands(parent,children);
1035 }
1036
1037 void DocParser::handleImage(DocNodeVariant *parent, DocNodeList &children)
1038 {
1039   bool inlineImage = false;
1040   QCString anchorStr;
1041
1042   int tok=tokenizer.lex();
1043   if (tok!=TK_WHITESPACE)
1044   {
1045     if (tok==TK_WORD)
1046     {
1047       if (context.token->name == "{")
1048       {
1049         tokenizer.setStateOptions();
1050         tok=tokenizer.lex();
1051         tokenizer.setStatePara();
1052         StringVector optList=split(context.token->name.str(),",");
1053         for (const auto &opt : optList)
1054         {
1055           if (opt.empty()) continue;
1056           QCString locOpt(opt);
1057           QCString locOptLow;
1058           locOpt = locOpt.stripWhiteSpace();
1059           locOptLow = locOpt.lower();
1060           if (locOptLow == "inline")
1061           {
1062             inlineImage = true;
1063           }
1064           else if (locOptLow.startsWith("anchor:"))
1065           {
1066             if (!anchorStr.isEmpty())
1067             {
1068                warn_doc_error(context.fileName,tokenizer.getLineNr(),
1069                   "multiple use of option 'anchor' for 'image' command, ignoring: '%s'",
1070                   qPrint(locOpt.mid(7)));
1071             }
1072             else
1073             {
1074                anchorStr = locOpt.mid(7);
1075             }
1076           }
1077           else
1078           {
1079             warn_doc_error(context.fileName,tokenizer.getLineNr(),
1080                   "unknown option '%s' for 'image' command specified",
1081                   qPrint(locOpt));
1082           }
1083         }
1084         tok=tokenizer.lex();
1085         if (tok!=TK_WHITESPACE)
1086         {
1087           warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1088           return;
1089         }
1090       }
1091     }
1092     else
1093     {
1094       warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1095       return;
1096     }
1097   }
1098   tok=tokenizer.lex();
1099   if (tok!=TK_WORD && tok!=TK_LNKWORD)
1100   {
1101     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1102         DocTokenizer::tokToString(tok));
1103     return;
1104   }
1105   tok=tokenizer.lex();
1106   if (tok!=TK_WHITESPACE)
1107   {
1108     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1109     return;
1110   }
1111   DocImage::Type t;
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;
1118   else
1119   {
1120     warn_doc_error(context.fileName,tokenizer.getLineNr(),"output format `%s` specified as the first argument of "
1121         "\\image command is not valid",
1122         qPrint(imgType));
1123     return;
1124   }
1125   tokenizer.setStateFile();
1126   tok=tokenizer.lex();
1127   tokenizer.setStatePara();
1128   if (tok!=TK_WORD)
1129   {
1130     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1131         DocTokenizer::tokToString(tok));
1132     return;
1133   }
1134   if (!anchorStr.isEmpty())
1135   {
1136     children.append<DocAnchor>(this,parent,anchorStr,true);
1137   }
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);
1142 }
1143
1144
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.
1155  */
1156 bool DocParser::defaultHandleToken(DocNodeVariant *parent,int tok, DocNodeList &children,bool handleWord)
1157 {
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
1161      )
1162   {
1163     DBG((" name=%s",qPrint(context.token->name)));
1164   }
1165   DBG(("\n"));
1166 reparsetoken:
1167   QCString tokenName = context.token->name;
1168   switch (tok)
1169   {
1170     case TK_COMMAND_AT:
1171         // fall through
1172     case TK_COMMAND_BS:
1173       switch (Mappers::cmdMapper->map(tokenName))
1174       {
1175         case CMD_BSLASH:
1176           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_BSlash);
1177           break;
1178         case CMD_AT:
1179           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_At);
1180           break;
1181         case CMD_LESS:
1182           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Less);
1183           break;
1184         case CMD_GREATER:
1185           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Greater);
1186           break;
1187         case CMD_AMP:
1188           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Amp);
1189           break;
1190         case CMD_DOLLAR:
1191           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dollar);
1192           break;
1193         case CMD_HASH:
1194           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Hash);
1195           break;
1196         case CMD_DCOLON:
1197           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_DoubleColon);
1198           break;
1199         case CMD_PERCENT:
1200           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Percent);
1201           break;
1202         case CMD_NDASH:
1203           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1204           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1205           break;
1206         case CMD_MDASH:
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);
1210           break;
1211         case CMD_QUOTE:
1212           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Quot);
1213           break;
1214         case CMD_PUNT:
1215           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dot);
1216           break;
1217         case CMD_PLUS:
1218           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Plus);
1219           break;
1220         case CMD_MINUS:
1221           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1222           break;
1223         case CMD_EQUAL:
1224           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Equal);
1225           break;
1226         case CMD_EMPHASIS:
1227           {
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)
1234             {
1235               DBG(("CMD_EMPHASIS: reparsing command %s\n",qPrint(context.token->name)));
1236               goto reparsetoken;
1237             }
1238           }
1239           break;
1240         case CMD_BOLD:
1241           {
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)
1248             {
1249               DBG(("CMD_BOLD: reparsing command %s\n",qPrint(context.token->name)));
1250               goto reparsetoken;
1251             }
1252           }
1253           break;
1254         case CMD_CODE:
1255           {
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)
1262             {
1263               DBG(("CMD_CODE: reparsing command %s\n",qPrint(context.token->name)));
1264               goto reparsetoken;
1265             }
1266           }
1267           break;
1268         case CMD_HTMLONLY:
1269           {
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();
1275           }
1276           break;
1277         case CMD_MANONLY:
1278           {
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();
1284           }
1285           break;
1286         case CMD_RTFONLY:
1287           {
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();
1293           }
1294           break;
1295         case CMD_LATEXONLY:
1296           {
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();
1302           }
1303           break;
1304         case CMD_XMLONLY:
1305           {
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();
1311           }
1312           break;
1313         case CMD_DBONLY:
1314           {
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();
1320           }
1321           break;
1322         case CMD_FORMULA:
1323           {
1324             children.append<DocFormula>(this,parent,context.token->id);
1325           }
1326           break;
1327         case CMD_ANCHOR:
1328           {
1329             handleAnchor(parent,children);
1330           }
1331           break;
1332         case CMD_INTERNALREF:
1333           {
1334             handleInternalRef(parent,children);
1335             tokenizer.setStatePara();
1336           }
1337           break;
1338         case CMD_SETSCOPE:
1339           {
1340             QCString scope;
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();
1347           }
1348           break;
1349         case CMD_IMAGE:
1350           handleImage(parent,children);
1351           break;
1352         default:
1353           return FALSE;
1354       }
1355       break;
1356     case TK_HTMLTAG:
1357       {
1358         switch (Mappers::htmlTagMapper->map(tokenName))
1359         {
1360           case HTML_DIV:
1361             warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <div> tag in heading\n");
1362             break;
1363           case HTML_PRE:
1364             warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <pre> tag in heading\n");
1365             break;
1366           case HTML_BOLD:
1367             if (!context.token->endTag)
1368             {
1369               handleStyleEnter(parent,children,DocStyleChange::Bold,tokenName,&context.token->attribs);
1370             }
1371             else
1372             {
1373               handleStyleLeave(parent,children,DocStyleChange::Bold,tokenName);
1374             }
1375             break;
1376           case HTML_S:
1377             if (!context.token->endTag)
1378             {
1379               handleStyleEnter(parent,children,DocStyleChange::S,tokenName,&context.token->attribs);
1380             }
1381             else
1382             {
1383               handleStyleLeave(parent,children,DocStyleChange::S,tokenName);
1384             }
1385             break;
1386           case HTML_STRIKE:
1387             if (!context.token->endTag)
1388             {
1389               handleStyleEnter(parent,children,DocStyleChange::Strike,tokenName,&context.token->attribs);
1390             }
1391             else
1392             {
1393               handleStyleLeave(parent,children,DocStyleChange::Strike,tokenName);
1394             }
1395             break;
1396           case HTML_DEL:
1397             if (!context.token->endTag)
1398             {
1399               handleStyleEnter(parent,children,DocStyleChange::Del,tokenName,&context.token->attribs);
1400             }
1401             else
1402             {
1403               handleStyleLeave(parent,children,DocStyleChange::Del,tokenName);
1404             }
1405             break;
1406           case HTML_UNDERLINE:
1407             if (!context.token->endTag)
1408             {
1409               handleStyleEnter(parent,children,DocStyleChange::Underline,tokenName,&context.token->attribs);
1410             }
1411             else
1412             {
1413               handleStyleLeave(parent,children,DocStyleChange::Underline,tokenName);
1414             }
1415             break;
1416           case HTML_INS:
1417             if (!context.token->endTag)
1418             {
1419               handleStyleEnter(parent,children,DocStyleChange::Ins,tokenName,&context.token->attribs);
1420             }
1421             else
1422             {
1423               handleStyleLeave(parent,children,DocStyleChange::Ins,tokenName);
1424             }
1425             break;
1426           case HTML_CODE:
1427           case XML_C:
1428             if (!context.token->endTag)
1429             {
1430               handleStyleEnter(parent,children,DocStyleChange::Code,tokenName,&context.token->attribs);
1431             }
1432             else
1433             {
1434               handleStyleLeave(parent,children,DocStyleChange::Code,tokenName);
1435             }
1436             break;
1437           case HTML_EMPHASIS:
1438             if (!context.token->endTag)
1439             {
1440               handleStyleEnter(parent,children,DocStyleChange::Italic,tokenName,&context.token->attribs);
1441             }
1442             else
1443             {
1444               handleStyleLeave(parent,children,DocStyleChange::Italic,tokenName);
1445             }
1446             break;
1447           case HTML_SUB:
1448             if (!context.token->endTag)
1449             {
1450               handleStyleEnter(parent,children,DocStyleChange::Subscript,tokenName,&context.token->attribs);
1451             }
1452             else
1453             {
1454               handleStyleLeave(parent,children,DocStyleChange::Subscript,tokenName);
1455             }
1456             break;
1457           case HTML_SUP:
1458             if (!context.token->endTag)
1459             {
1460               handleStyleEnter(parent,children,DocStyleChange::Superscript,tokenName,&context.token->attribs);
1461             }
1462             else
1463             {
1464               handleStyleLeave(parent,children,DocStyleChange::Superscript,tokenName);
1465             }
1466             break;
1467           case HTML_CENTER:
1468             if (!context.token->endTag)
1469             {
1470               handleStyleEnter(parent,children,DocStyleChange::Center,tokenName,&context.token->attribs);
1471             }
1472             else
1473             {
1474               handleStyleLeave(parent,children,DocStyleChange::Center,tokenName);
1475             }
1476             break;
1477           case HTML_SMALL:
1478             if (!context.token->endTag)
1479             {
1480               handleStyleEnter(parent,children,DocStyleChange::Small,tokenName,&context.token->attribs);
1481             }
1482             else
1483             {
1484               handleStyleLeave(parent,children,DocStyleChange::Small,tokenName);
1485             }
1486             break;
1487           case HTML_CITE:
1488             if (!context.token->endTag)
1489             {
1490               handleStyleEnter(parent,children,DocStyleChange::Cite,tokenName,&context.token->attribs);
1491             }
1492             else
1493             {
1494               handleStyleLeave(parent,children,DocStyleChange::Cite,tokenName);
1495             }
1496             break;
1497           case HTML_IMG:
1498             if (!context.token->endTag)
1499               handleImg(parent,children,context.token->attribs);
1500             break;
1501           default:
1502             return FALSE;
1503             break;
1504         }
1505       }
1506       break;
1507     case TK_SYMBOL:
1508       {
1509         HtmlEntityMapper::SymType s = DocSymbol::decodeSymbol(tokenName);
1510         if (s!=HtmlEntityMapper::Sym_Unknown)
1511         {
1512           children.append<DocSymbol>(this,parent,s);
1513         }
1514         else
1515         {
1516           return FALSE;
1517         }
1518       }
1519       break;
1520     case TK_WHITESPACE:
1521     case TK_NEWPARA:
1522 handlepara:
1523       if (insidePRE(parent) || !children.empty())
1524       {
1525         children.append<DocWhiteSpace>(this,parent,context.token->chars);
1526       }
1527       break;
1528     case TK_LNKWORD:
1529       if (handleWord)
1530       {
1531         handleLinkedWord(parent,children);
1532       }
1533       else
1534         return FALSE;
1535       break;
1536     case TK_WORD:
1537       if (handleWord)
1538       {
1539         children.append<DocWord>(this,parent,context.token->name);
1540       }
1541       else
1542         return FALSE;
1543       break;
1544     case TK_URL:
1545       if (context.insideHtmlLink)
1546       {
1547         children.append<DocWord>(this,parent,context.token->name);
1548       }
1549       else
1550       {
1551         children.append<DocURL>(this,parent,context.token->name,context.token->isEMailAddr);
1552       }
1553       break;
1554     default:
1555       return FALSE;
1556   }
1557   return TRUE;
1558 }
1559
1560 //---------------------------------------------------------------------------
1561
1562 void DocParser::handleImg(DocNodeVariant *parent, DocNodeList &children,const HtmlAttribList &tagHtmlAttribs)
1563 {
1564   bool found=FALSE;
1565   uint index=0;
1566   for (const auto &opt : tagHtmlAttribs)
1567   {
1568     //printf("option name=%s value=%s\n",qPrint(opt.name),qPrint(opt.value));
1569     if (opt.name=="src" && !opt.value.isEmpty())
1570     {
1571       // copy attributes
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),
1579             t,opt.value);
1580       found = TRUE;
1581     }
1582     ++index;
1583   }
1584   if (!found)
1585   {
1586     warn_doc_error(context.fileName,tokenizer.getLineNr(),"IMG tag does not have a SRC attribute!\n");
1587   }
1588 }
1589
1590 //---------------------------------------------------------------------------
1591
1592 int DocParser::internalValidatingParseDoc(DocNodeVariant *parent,DocNodeList &children,
1593                                     const QCString &doc)
1594 {
1595   int retval = RetVal_OK;
1596
1597   if (doc.isEmpty()) return retval;
1598
1599   tokenizer.init(doc.data(),context.fileName,context.markdownSupport);
1600
1601   // first parse any number of paragraphs
1602   bool isFirst=TRUE;
1603   DocPara *lastPar=!children.empty() ? std::get_if<DocPara>(&children.back()): 0;
1604   if (lastPar)
1605   { // last child item was a paragraph
1606     isFirst=FALSE;
1607   }
1608   do
1609   {
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())
1615     {
1616       if (lastPar) lastPar->markLast(FALSE);
1617       lastPar=par;
1618     }
1619     else
1620     {
1621       children.pop_back();
1622     }
1623   } while (retval==TK_NEWPARA);
1624   if (lastPar) lastPar->markLast();
1625
1626   //printf("internalValidateParsingDoc: %p: isFirst=%d isLast=%d\n",
1627   //   lastPar,lastPar?lastPar->isFirst():-1,lastPar?lastPar->isLast():-1);
1628
1629   return retval;
1630 }
1631
1632 //---------------------------------------------------------------------------
1633
1634 void DocParser::readTextFileByName(const QCString &file,QCString &text)
1635 {
1636   if (Portable::isAbsolutePath(file))
1637   {
1638     FileInfo fi(file.str());
1639     if (fi.exists())
1640     {
1641       text = fileToString(file,Config_getBool(FILTER_SOURCE_FILES));
1642       return;
1643     }
1644   }
1645   const StringVector &examplePathList = Config_getList(EXAMPLE_PATH);
1646   for (const auto &s : examplePathList)
1647   {
1648     std::string absFileName = s+(Portable::pathSeparator()+file).str();
1649     FileInfo fi(absFileName);
1650     if (fi.exists())
1651     {
1652       text = fileToString(QCString(absFileName),Config_getBool(FILTER_SOURCE_FILES));
1653       return;
1654     }
1655   }
1656
1657   // as a fallback we also look in the exampleNameDict
1658   bool ambig;
1659   FileDef *fd = findFileDef(Doxygen::exampleNameLinkedMap,file,ambig);
1660   if (fd)
1661   {
1662     text = fileToString(fd->absFilePath(),Config_getBool(FILTER_SOURCE_FILES));
1663     if (ambig)
1664     {
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))
1668           );
1669     }
1670   }
1671   else
1672   {
1673     warn_doc_error(context.fileName,tokenizer.getLineNr(),"included file %s is not found. "
1674            "Check your EXAMPLE_PATH",qPrint(file));
1675   }
1676 }
1677
1678 //---------------------------------------------------------------------------
1679
1680 static QCString extractCopyDocId(const char *data, uint &j, uint len)
1681 {
1682   uint s=j;
1683   int round=0;
1684   bool insideDQuote=FALSE;
1685   bool insideSQuote=FALSE;
1686   bool found=FALSE;
1687   while (j<len && !found)
1688   {
1689     if (!insideSQuote && !insideDQuote)
1690     {
1691       switch (data[j])
1692       {
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
1699         case '\n':
1700           found=(round==0);
1701           break;
1702       }
1703     }
1704     else if (insideSQuote) // look for single quote end
1705     {
1706       if (data[j]=='\'' && (j==0 || data[j]!='\\'))
1707       {
1708         insideSQuote=FALSE;
1709       }
1710     }
1711     else if (insideDQuote) // look for double quote end
1712     {
1713       if (data[j]=='"' && (j==0 || data[j]!='\\'))
1714       {
1715         insideDQuote=FALSE;
1716       }
1717     }
1718     if (!found) j++;
1719   }
1720   if (qstrncmp(data+j," const",6)==0)
1721   {
1722     j+=6;
1723   }
1724   else if (qstrncmp(data+j," volatile",9)==0)
1725   {
1726     j+=9;
1727   }
1728   uint e=j;
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]);
1732   return id;
1733 }
1734
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)
1742
1743 static uint isCopyBriefOrDetailsCmd(const char *data, uint i,uint len,bool &brief)
1744 {
1745   uint j=0;
1746   if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1747   {
1748     CHECK_FOR_COMMAND("copybrief",brief=TRUE);    // @copybrief or \copybrief
1749     CHECK_FOR_COMMAND("copydetails",brief=FALSE); // @copydetails or \copydetails
1750   }
1751   return j;
1752 }
1753
1754 static uint isVerbatimSection(const char *data,uint i,uint len,QCString &endMarker)
1755 {
1756   uint j=0;
1757   if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1758   {
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");
1773   }
1774   //printf("isVerbatimSection(%s)=%d)\n",qPrint(QCString(&data[i]).left(10)),j);
1775   return j;
1776 }
1777
1778 static uint skipToEndMarker(const char *data,uint i,uint len,const QCString &endMarker)
1779 {
1780   while (i<len)
1781   {
1782     if ((data[i]=='@' || data[i]=='\\') &&  // start of command character
1783         (i==0 || (data[i-1]!='@' && data[i-1]!='\\'))) // that is not escaped
1784     {
1785       if (i+endMarker.length()+1<=len && qstrncmp(data+i+1,endMarker.data(),endMarker.length())==0)
1786       {
1787         return i+endMarker.length()+1;
1788       }
1789     }
1790     i++;
1791   }
1792   // oops no endmarker found...
1793   return i<len ? i+1 : len;
1794 }
1795
1796
1797 QCString DocParser::processCopyDoc(const char *data,uint &len)
1798 {
1799   //printf("processCopyDoc start '%s'\n",data);
1800   GrowBuf buf;
1801   uint i=0;
1802   int lineNr = tokenizer.getLineNr();
1803   while (i<len)
1804   {
1805     char c = data[i];
1806     if (c=='@' || c=='\\') // look for a command
1807     {
1808       bool isBrief=TRUE;
1809       uint j=isCopyBriefOrDetailsCmd(data,i,len,isBrief);
1810       if (j>0)
1811       {
1812         // skip whitespace
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;
1817         QCString doc,brief;
1818         //printf("resolving docs='%s'\n",qPrint(id));
1819         if (findDocsForMemberOrCompound(id,&doc,&brief,&def))
1820         {
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
1824           {
1825             QCString orgFileName = context.fileName;
1826             context.copyStack.push_back(def);
1827             if (isBrief)
1828             {
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));
1833             }
1834             else
1835             {
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));
1840             }
1841             context.copyStack.pop_back();
1842             buf.addStr("\\ifile \""+context.fileName+"\" ");
1843             buf.addStr("\\iline "+QCString().setNum(lineNr)+" ");
1844           }
1845           else
1846           {
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));
1850           }
1851         }
1852         else
1853         {
1854           warn_doc_error(context.fileName,tokenizer.getLineNr(),
1855                "@copy%s or @copydoc target '%s' not found", isBrief?"brief":"details",
1856                qPrint(id));
1857         }
1858         // skip over command
1859         i=j;
1860       }
1861       else
1862       {
1863         QCString endMarker;
1864         uint k = isVerbatimSection(data,i,len,endMarker);
1865         if (k>0)
1866         {
1867           uint orgPos = i;
1868           i=skipToEndMarker(data,k,len,endMarker);
1869           buf.addStr(data+orgPos,i-orgPos);
1870           // TODO: adjust lineNr
1871         }
1872         else
1873         {
1874           buf.addChar(c);
1875           i++;
1876         }
1877       }
1878     }
1879     else // not a command, just copy
1880     {
1881       buf.addChar(c);
1882       i++;
1883       lineNr += (c=='\n') ? 1 : 0;
1884     }
1885   }
1886   len = static_cast<uint>(buf.getPos());
1887   buf.addChar(0);
1888   return buf.get();
1889 }
1890
1891
1892 //---------------------------------------------------------------------------
1893
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)
1901 {
1902   DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
1903   assert(parser!=0);
1904   if (parser==0) return 0;
1905   //printf("validatingParseDoc(%s,%s)=[%s]\n",ctx?qPrint(ctx->name()):"<none>",
1906   //                                     md?qPrint(md->name()):"<none>",
1907   //                                     input);
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;
1911
1912   // store parser state so we can re-enter this function if needed
1913   //bool fortranOpt = Config_getBool(OPTIMIZE_FOR_FORTRAN);
1914   parser->pushContext();
1915
1916   if (ctx && ctx!=Doxygen::globalScope &&
1917       (ctx->definitionType()==Definition::TypeClass ||
1918        ctx->definitionType()==Definition::TypeNamespace
1919       )
1920      )
1921   {
1922     parser->context.context = ctx->name();
1923   }
1924   else if (ctx && ctx->definitionType()==Definition::TypePage)
1925   {
1926     const Definition *scope = (toPageDef(ctx))->getPageScope();
1927     if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1928   }
1929   else if (ctx && ctx->definitionType()==Definition::TypeGroup)
1930   {
1931     const Definition *scope = (toGroupDef(ctx))->getGroupScope();
1932     if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1933   }
1934   else
1935   {
1936     parser->context.context = "";
1937   }
1938   parser->context.scope = ctx;
1939
1940   if (indexWords && Doxygen::searchIndex)
1941   {
1942     if (md)
1943     {
1944       parser->context.searchUrl=md->getOutputFileBase();
1945       parser->searchData.setCurrentDoc(md,md->anchor(),false);
1946     }
1947     else if (ctx)
1948     {
1949       parser->context.searchUrl=ctx->getOutputFileBase();
1950       parser->searchData.setCurrentDoc(ctx,ctx->anchor(),false);
1951     }
1952   }
1953   else
1954   {
1955     parser->context.searchUrl="";
1956   }
1957
1958   parser->context.fileName = fileName;
1959   parser->context.relPath = (!linkFromIndex && ctx) ?
1960                QCString(relativePathToRoot(ctx->getOutputFileBase())) :
1961                QCString("");
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;
1980
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')
1986   {
1987     inpStr+='\n';
1988   }
1989   //printf("processCopyDoc(in='%s' out='%s')\n",input,qPrint(inpStr));
1990   parser->tokenizer.init(inpStr.data(),parser->context.fileName,markdownSupport);
1991
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);
1995
1996   if (Debug::isFlagSet(Debug::PrintTree))
1997   {
1998     // pretty print the result
1999     std::visit(PrintDocVisitor{},ast->root);
2000   }
2001
2002   parser->checkUnOrMultipleDocumentedParams();
2003   if (parser->context.memberDef) parser->context.memberDef->detectUndocumentedParams(parser->context.hasParamCommand,parser->context.hasReturnCommand);
2004
2005   // TODO: These should be called at the end of the program.
2006   //parser->tokenizer.cleanup();
2007   //Mappers::cmdMapper->freeInstance();
2008   //Mappers::htmlTagMapper->freeInstance();
2009
2010   // restore original parser state
2011   parser->popContext();
2012
2013   //printf(">>>>>> end validatingParseDoc(%s,%s)\n",ctx?qPrint(ctx->name()):"<none>",
2014   //                                     md?qPrint(md->name()):"<none>");
2015
2016   return ast;
2017 }
2018
2019 IDocNodeASTPtr validatingParseText(IDocParser &parserIntf,const QCString &input)
2020 {
2021   DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2022   assert(parser!=0);
2023   if (parser==0) return 0;
2024
2025   // store parser state so we can re-enter this function if needed
2026   parser->pushContext();
2027
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="";
2051
2052
2053   auto ast = std::make_unique<DocNodeAST>(DocText(parser));
2054
2055   if (!input.isEmpty())
2056   {
2057     parser->tokenizer.setLineNr(1);
2058     parser->tokenizer.init(input.data(),parser->context.fileName,Config_getBool(MARKDOWN_SUPPORT));
2059
2060     // build abstract syntax tree
2061     std::get<DocText>(ast->root).parse(&ast->root);
2062
2063     if (Debug::isFlagSet(Debug::PrintTree))
2064     {
2065       // pretty print the result
2066       std::visit(PrintDocVisitor{},ast->root);
2067     }
2068   }
2069
2070   // restore original parser state
2071   parser->popContext();
2072   return ast;
2073 }
2074
2075 IDocNodeASTPtr createRef(IDocParser &parserIntf,const QCString &target,const QCString &context)
2076 {
2077   DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2078   assert(parser!=0);
2079   if (parser==0) return 0;
2080   return std::make_unique<DocNodeAST>(DocRef(parser,0,target,context));
2081 }
2082
2083 void docFindSections(const QCString &input,
2084                      const Definition *d,
2085                      const QCString &fileName)
2086 {
2087   DocParser parser;
2088   parser.tokenizer.findSections(input,d,fileName);
2089 }
2090