dcdd9ef2f484869de7f48ecd05a4408aa24c59bb
[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 #include "trace.h"
41
42 #if !ENABLE_DOCPARSER_TRACING
43 #undef  AUTO_TRACE
44 #undef  AUTO_TRACE_ADD
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
49 #endif
50
51
52 //---------------------------------------------------------------------------
53
54 IDocParserPtr createDocParser()
55 {
56   return std::make_unique<DocParser>();
57 }
58
59 //---------------------------------------------------------------------------
60 DocParser::~DocParser()
61 {
62 }
63
64 void DocParser::pushContext()
65 {
66   //QCString indent;
67   //indent.fill(' ',contextStack.size()*2+2);
68   //printf("%sdocParserPushContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
69
70   tokenizer.pushContext();
71   contextStack.push(DocParserContext());
72   auto &ctx = contextStack.top();
73   ctx = context;
74   ctx.lineNo = tokenizer.getLineNr();
75   context.token = tokenizer.token();
76 }
77
78 void DocParser::popContext()
79 {
80   auto &ctx = contextStack.top();
81   context = ctx;
82   tokenizer.setLineNr(ctx.lineNo);
83   contextStack.pop();
84   tokenizer.popContext();
85   context.token = tokenizer.token();
86
87   //QCString indent;
88   //indent.fill(' ',contextStack.size()*2+2);
89   //printf("%sdocParserPopContext() count=%zu\n",qPrint(indent),context.nodeStack.size());
90 }
91
92 //---------------------------------------------------------------------------
93
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
96  * parameter).
97  */
98 QCString DocParser::findAndCopyImage(const QCString &fileName, DocImage::Type type, bool doWarn)
99 {
100   QCString result;
101   bool ambig;
102   FileDef *fd = findFileDef(Doxygen::imageNameLinkedMap,fileName,ambig);
103   //printf("Search for %s\n",fileName);
104   if (fd)
105   {
106     if (ambig & doWarn)
107     {
108       QCString text;
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));
113     }
114
115     QCString inputFile = fd->absFilePath();
116     FileInfo infi(inputFile.str());
117     if (infi.exists())
118     {
119       result = fileName;
120       int i;
121       if ((i=result.findRev('/'))!=-1 || (i=result.findRev('\\'))!=-1)
122       {
123         result = result.right(static_cast<int>(result.length())-i-1);
124       }
125       //printf("fileName=%s result=%s\n",fileName,qPrint(result));
126       QCString outputDir;
127       switch(type)
128       {
129         case DocImage::Html:
130           if (!Config_getBool(GENERATE_HTML)) return result;
131           outputDir = Config_getString(HTML_OUTPUT);
132           break;
133         case DocImage::Latex:
134           if (!Config_getBool(GENERATE_LATEX)) return result;
135           outputDir = Config_getString(LATEX_OUTPUT);
136           break;
137         case DocImage::DocBook:
138           if (!Config_getBool(GENERATE_DOCBOOK)) return result;
139           outputDir = Config_getString(DOCBOOK_OUTPUT);
140           break;
141         case DocImage::Rtf:
142           if (!Config_getBool(GENERATE_RTF)) return result;
143           outputDir = Config_getString(RTF_OUTPUT);
144           break;
145         case DocImage::Xml:
146           if (!Config_getBool(GENERATE_XML)) return result;
147           outputDir = Config_getString(XML_OUTPUT);
148           break;
149       }
150       QCString outputFile = outputDir+"/"+result;
151       FileInfo outfi(outputFile.str());
152       if (outfi.isSymLink())
153       {
154         Dir().remove(outputFile.str());
155         warn_doc_error(context.fileName,tokenizer.getLineNr(),
156             "destination of image %s is a symlink, replacing with image",
157             qPrint(outputFile));
158       }
159       if (outputFile!=inputFile) // prevent copying to ourself
160       {
161         if (copyFile(inputFile,outputFile) && type==DocImage::Html)
162         {
163           Doxygen::indexList->addImageFile(result);
164         }
165       }
166     }
167     else
168     {
169       warn_doc_error(context.fileName,tokenizer.getLineNr(),
170           "could not open image %s",qPrint(fileName));
171     }
172
173     if (type==DocImage::Latex && Config_getBool(USE_PDFLATEX) &&
174         fd->name().endsWith(".eps")
175        )
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)
184       {
185         err("Problems running epstopdf. Check your TeX installation!\n");
186       }
187       return baseName;
188     }
189   }
190   else
191   {
192     result=fileName;
193     if (!result.startsWith("http:") && !result.startsWith("https:") && doWarn)
194     {
195       warn_doc_error(context.fileName,tokenizer.getLineNr(),
196            "image file %s is not found in IMAGE_PATH: "
197            "assuming external image.",qPrint(fileName)
198           );
199     }
200   }
201   return result;
202 }
203
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).
209  */
210 void DocParser::checkArgumentName()
211 {
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
221
222   static const reg::Ex re(R"(\$?\w+\.*)");
223   reg::Iterator it(name,re);
224   reg::Iterator end;
225   for (; it!=end ; ++it)
226   {
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));
231     bool found=FALSE;
232     for (const Argument &a : al)
233     {
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);
239       if (aName==argName)
240       {
241         context.paramsFound.insert(aName.str());
242         found=TRUE;
243         break;
244       }
245     }
246     if (!found)
247     {
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
256       {
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();
262       }
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));
269     }
270   }
271 }
272 /*! Collects the return values found with \@retval command
273  *  in a global list g_parserContext.retvalsFound.
274  */
275 void DocParser::checkRetvalName()
276 {
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
281   {
282      warn_doc_error(context.memberDef->getDefFileName(),
283                     context.memberDef->getDefLine(),
284                     "%s",
285                     qPrint("return value '" + name + "' of " +
286                     QCString(context.memberDef->qualifiedName()) +
287                     " has multiple documentation sections"));
288   }
289   context.retvalsFound.insert(name.str());
290 }
291
292 /*! Checks if the parameters that have been specified using \@param are
293  *  indeed all parameters and that a parameter does not have multiple
294  *  \@param blocks.
295  *  Must be called after checkArgumentName() has been called for each
296  *  argument.
297  */
298 void DocParser::checkUnOrMultipleDocumentedParams()
299 {
300   if (context.memberDef && context.hasParamCommand)
301   {
302     const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
303       context.memberDef->argumentList() :
304       context.memberDef->declArgumentList();
305     SrcLangExt lang = context.memberDef->getLanguage();
306     if (!al.empty())
307     {
308       ArgumentList undocParams;
309       for (const Argument &a: al)
310       {
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"))
317         {
318           // allow undocumented self / cls parameter for Python
319         }
320         else if (!argName.isEmpty())
321         {
322           size_t count = context.paramsFound.count(argName.str());
323           if (count==0 && a.docs.isEmpty())
324           {
325             undocParams.push_back(a);
326           }
327           else if (count>1 && Config_getBool(WARN_IF_DOC_ERROR))
328           {
329             warn_doc_error(context.memberDef->docFile(),
330                            context.memberDef->docLine(),
331                            "%s",
332                            qPrint("argument '" + aName +
333                            "' from the argument list of " +
334                            QCString(context.memberDef->qualifiedName()) +
335                            " has multiple @param documentation sections"));
336           }
337         }
338       }
339       if (!undocParams.empty() && Config_getBool(WARN_IF_INCOMPLETE_DOC))
340       {
341         bool first=TRUE;
342         QCString errMsg = "The following parameter";
343         if (undocParams.size()>1) errMsg+="s";
344         errMsg+=" of "+
345             QCString(context.memberDef->qualifiedName()) +
346             QCString(argListToString(al)) +
347             (undocParams.size()>1 ? " are" : " is") + " not documented:\n";
348         for (const Argument &a : undocParams)
349         {
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";
354           first=FALSE;
355           errMsg+="  parameter '"+argName+"'";
356         }
357         warn_incomplete_doc(context.memberDef->docFile(),
358                             context.memberDef->docLine(),
359                             "%s",
360                             qPrint(substitute(errMsg,"%","%%")));
361       }
362     }
363     else
364     {
365       if (context.paramsFound.empty() && Config_getBool(WARN_IF_DOC_ERROR))
366       {
367         warn_doc_error(context.memberDef->docFile(),
368                        context.memberDef->docLine(),
369                        "%s",
370                        qPrint(context.memberDef->qualifiedName() +
371                               " has @param documentation sections but no arguments"));
372       }
373     }
374   }
375 }
376
377
378 //---------------------------------------------------------------------------
379
380 //---------------------------------------------------------------------------
381
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
386  *  put in pDef.
387  *  @retval TRUE if name was found.
388  *  @retval FALSE if name was not found.
389  */
390 bool DocParser::findDocsForMemberOrCompound(const QCString &commandName,
391                                             QCString *pDoc,
392                                             QCString *pBrief,
393                                             const Definition **pDef)
394 {
395   //printf("findDocsForMemberOrCompound(%s)\n",commandName);
396   *pDoc="";
397   *pBrief="";
398   *pDef=0;
399   QCString cmdArg=commandName;
400   if (cmdArg.isEmpty()) return FALSE;
401
402   const FileDef      *fd=0;
403   const GroupDef     *gd=0;
404   const PageDef      *pd=0;
405   gd = Doxygen::groupLinkedMap->find(cmdArg);
406   if (gd) // group
407   {
408     *pDoc=gd->documentation();
409     *pBrief=gd->briefDescription();
410     *pDef=gd;
411     return TRUE;
412   }
413   pd = Doxygen::pageLinkedMap->find(cmdArg);
414   if (pd) // page
415   {
416     *pDoc=pd->documentation();
417     *pBrief=pd->briefDescription();
418     *pDef=pd;
419     return TRUE;
420   }
421   bool ambig;
422   fd = findFileDef(Doxygen::inputNameLinkedMap,cmdArg,ambig);
423   if (fd && !ambig) // file
424   {
425     *pDoc=fd->documentation();
426     *pBrief=fd->briefDescription();
427     *pDef=fd;
428     return TRUE;
429   }
430
431   // for symbols we need to normalize the separator, so A#B, or A\B, or A.B becomes A::B
432   cmdArg = substitute(cmdArg,"#","::");
433   cmdArg = substitute(cmdArg,"\\","::");
434   bool extractAnonNs = Config_getBool(EXTRACT_ANON_NSPACES);
435   if (extractAnonNs &&
436       cmdArg.startsWith("anonymous_namespace{")
437       )
438   {
439     size_t rightBracePos = cmdArg.find("}", static_cast<int>(qstrlen("anonymous_namespace{")));
440     QCString leftPart = cmdArg.left(rightBracePos + 1);
441     QCString rightPart = cmdArg.right(cmdArg.size() - rightBracePos - 1);
442     rightPart = substitute(rightPart, ".", "::");
443     cmdArg = leftPart + rightPart;
444   }
445   else
446   {
447     cmdArg = substitute(cmdArg,".","::");
448   }
449
450   int l=static_cast<int>(cmdArg.length());
451
452   int funcStart=cmdArg.find('(');
453   if (funcStart==-1)
454   {
455     funcStart=l;
456   }
457   else
458   {
459     // Check for the case of operator() and the like.
460     // beware of scenarios like operator()((foo)bar)
461     int secondParen = cmdArg.find('(', funcStart+1);
462     int leftParen   = cmdArg.find(')', funcStart+1);
463     if (leftParen!=-1 && secondParen!=-1)
464     {
465       if (leftParen<secondParen)
466       {
467         funcStart=secondParen;
468       }
469     }
470   }
471
472   QCString name=removeRedundantWhiteSpace(cmdArg.left(funcStart));
473   QCString args=cmdArg.right(l-funcStart);
474   // try if the link is to a member
475   GetDefInput input(
476       context.context.find('.')==-1 ? context.context : QCString(), // find('.') is a hack to detect files
477       name,
478       args.isEmpty() ? QCString() : args);
479   input.checkCV=true;
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)
483   {
484     *pDoc=result.md->documentation();
485     *pBrief=result.md->briefDescription();
486     *pDef=result.md;
487     return TRUE;
488   }
489
490   int scopeOffset=static_cast<int>(context.context.length());
491   do // for each scope
492   {
493     QCString fullName=cmdArg;
494     if (scopeOffset>0)
495     {
496       fullName.prepend(context.context.left(scopeOffset)+"::");
497     }
498     //printf("Trying fullName='%s'\n",qPrint(fullName));
499
500     // try class, namespace, group, page, file reference
501     const ClassDef *cd = Doxygen::classLinkedMap->find(fullName);
502     if (cd) // class
503     {
504       *pDoc=cd->documentation();
505       *pBrief=cd->briefDescription();
506       *pDef=cd;
507       return TRUE;
508     }
509     const NamespaceDef *nd = Doxygen::namespaceLinkedMap->find(fullName);
510     if (nd) // namespace
511     {
512       *pDoc=nd->documentation();
513       *pBrief=nd->briefDescription();
514       *pDef=nd;
515       return TRUE;
516     }
517     if (scopeOffset==0)
518     {
519       scopeOffset=-1;
520     }
521     else
522     {
523       scopeOffset = context.context.findRev("::",scopeOffset-1);
524       if (scopeOffset==-1) scopeOffset=0;
525     }
526   } while (scopeOffset>=0);
527
528
529   return FALSE;
530 }
531
532 //---------------------------------------------------------------------------
533 void DocParser::errorHandleDefaultToken(DocNodeVariant *parent,int tok,
534                                         DocNodeList &children,const QCString &txt)
535 {
536   const char *cmd_start = "\\";
537   switch (tok)
538   {
539     case TK_COMMAND_AT:
540       cmd_start = "@";
541       // fall through
542     case TK_COMMAND_BS:
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));
546       break;
547     case TK_SYMBOL:
548       warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported symbol %s found as part of a %s",
549            qPrint(context.token->name), qPrint(txt));
550       break;
551     case TK_HTMLTAG:
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));
554       break;
555     default:
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));
559       break;
560   }
561 }
562
563 //---------------------------------------------------------------------------
564
565 int DocParser::handleStyleArgument(DocNodeVariant *parent,DocNodeList &children,const QCString &cmdName)
566 {
567   AUTO_TRACE("cmdName={}",cmdName);
568   QCString saveCmdName = cmdName;
569   int tok=tokenizer.lex();
570   if (tok!=TK_WHITESPACE)
571   {
572     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
573         qPrint(saveCmdName));
574     return tok;
575   }
576   while ((tok=tokenizer.lex()) &&
577           tok!=TK_WHITESPACE &&
578           tok!=TK_NEWPARA &&
579           tok!=TK_LISTITEM &&
580           tok!=TK_ENDLIST
581         )
582   {
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))
586     {
587       // special character that ends the markup command
588       return tok;
589     }
590     if (!defaultHandleToken(parent,tok,children))
591     {
592       switch (tok)
593       {
594         case TK_HTMLTAG:
595           if (insideLI(parent) && Mappers::htmlTagMapper->map(context.token->name) && context.token->endTag)
596           { // ignore </li> as the end of a style command
597             continue;
598           }
599           AUTO_TRACE_EXIT("end tok={}",DocTokenizer::tokToString(tok));
600           return tok;
601           break;
602         default:
603           errorHandleDefaultToken(parent,tok,children,"\\" + saveCmdName + " command");
604           break;
605       }
606       break;
607     }
608   }
609   AUTO_TRACE_EXIT("end tok={}",DocTokenizer::tokToString(tok));
610   return (tok==TK_NEWPARA || tok==TK_LISTITEM || tok==TK_ENDLIST
611          ) ? tok : RetVal_OK;
612 }
613
614 /*! Called when a style change starts. For instance a \<b\> command is
615  *  encountered.
616  */
617 void DocParser::handleStyleEnter(DocNodeVariant *parent,DocNodeList &children,
618           DocStyleChange::Style s,const QCString &tagName,const HtmlAttribList *attribs)
619 {
620   AUTO_TRACE("tagName={}",tagName);
621   children.append<DocStyleChange>(this,parent,context.nodeStack.size(),s,tagName,TRUE,attribs);
622   context.styleStack.push(&children.back());
623 }
624
625 /*! Called when a style change ends. For instance a \</b\> command is
626  *  encountered.
627  */
628 void DocParser::handleStyleLeave(DocNodeVariant *parent,DocNodeList &children,
629          DocStyleChange::Style s,const QCString &tagName)
630 {
631   AUTO_TRACE("tagName={}",tagName);
632   QCString tagNameLower = QCString(tagName).lower();
633
634   auto topStyleChange = [](const DocStyleChangeStack &stack) -> const DocStyleChange &
635   {
636     return std::get<DocStyleChange>(*stack.top());
637   };
638
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
643      )
644   {
645     if (context.styleStack.empty())
646     {
647       warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag without matching <%s>",
648           qPrint(tagName),qPrint(tagName));
649     }
650     else if (topStyleChange(context.styleStack).tagName()!=tagNameLower)
651     {
652       warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
653           qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
654     }
655     else if (topStyleChange(context.styleStack).style()!=s)
656     {
657       warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </%s> tag while expecting </%s>",
658           qPrint(tagName),qPrint(topStyleChange(context.styleStack).tagName()));
659     }
660     else
661     {
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());
664     }
665   }
666   else // end the section
667   {
668     children.append<DocStyleChange>(
669           this,parent,context.nodeStack.size(),s,
670           topStyleChange(context.styleStack).tagName(),FALSE);
671     context.styleStack.pop();
672   }
673 }
674
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.
678  */
679 void DocParser::handlePendingStyleCommands(DocNodeVariant *parent,DocNodeList &children)
680 {
681   AUTO_TRACE();
682   if (!context.styleStack.empty())
683   {
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;
692     }
693   }
694 }
695
696 void DocParser::handleInitialStyleCommands(DocNodeVariant *parent,DocNodeList &children)
697 {
698   AUTO_TRACE();
699   while (!context.initialStyleStack.empty())
700   {
701     const DocStyleChange &sc = std::get<DocStyleChange>(*context.initialStyleStack.top());
702     handleStyleEnter(parent,children,sc.style(),sc.tagName(),&sc.attribs());
703     context.initialStyleStack.pop();
704   }
705 }
706
707 int DocParser::handleAHref(DocNodeVariant *parent,DocNodeList &children,
708                            const HtmlAttribList &tagHtmlAttribs)
709 {
710   AUTO_TRACE();
711   uint32_t index=0;
712   int retval = RetVal_OK;
713   for (const auto &opt : tagHtmlAttribs)
714   {
715     if (opt.name=="name" || opt.name=="id") // <a name=label> or <a id=label> tag
716     {
717       if (!opt.value.isEmpty())
718       {
719         children.append<DocAnchor>(this,parent,opt.value,TRUE);
720         break; // stop looking for other tag attribs
721       }
722       else
723       {
724         warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <a> tag with name option but without value!");
725       }
726     }
727     else if (opt.name=="href") // <a href=url>..</a> tag
728     {
729       // copy attributes
730       HtmlAttribList attrList = tagHtmlAttribs;
731       // and remove the href attribute
732       attrList.erase(attrList.begin()+index);
733       QCString relPath;
734       if (opt.value.at(0) != '#') relPath = context.relPath;
735       children.append<DocHRef>(this, parent, attrList,
736                                opt.value, relPath,
737                                convertNameToFile(context.fileName, FALSE, TRUE));
738       context.insideHtmlLink=TRUE;
739       retval = children.get_last<DocHRef>()->parse();
740       context.insideHtmlLink=FALSE;
741       break;
742     }
743     else // unsupported option for tag a
744     {
745     }
746     ++index;
747   }
748   return retval;
749 }
750
751 void DocParser::handleUnclosedStyleCommands()
752 {
753   AUTO_TRACE();
754   if (!context.initialStyleStack.empty())
755   {
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));
762   }
763 }
764
765 void DocParser::handleLinkedWord(DocNodeVariant *parent,DocNodeList &children,bool ignoreAutoLinkFlag)
766 {
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
771   {
772     children.append<DocWord>(this,parent,name);
773     return;
774   }
775
776   // ------- try to turn the word 'name' into a link
777
778   const Definition *compound=0;
779   const MemberDef  *member=0;
780   uint32_t len = context.token->name.length();
781   ClassDef *cd=0;
782   bool ambig;
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))
789       )
790      )
791   {
792     //printf("resolveRef %s = %p (linkable?=%d)\n",qPrint(context.token->name),member,member ? member->isLinkable() : FALSE);
793     if (member && member->isLinkable()) // member link
794     {
795       if (member->isObjCMethod())
796       {
797         bool localLink = context.memberDef ? member->getClassDef()==context.memberDef->getClassDef() : FALSE;
798         name = member->objCMethodName(localLink,context.inSeeBlock);
799       }
800       children.append<DocLinkedWord>(
801             this,parent,name,
802             member->getReference(),
803             member->getOutputFileBase(),
804             member->anchor(),
805             member->briefDescriptionAsTooltip());
806     }
807     else if (compound->isLinkable()) // compound link
808     {
809       QCString anchor = compound->anchor();
810       if (compound->definitionType()==Definition::TypeFile)
811       {
812         name=context.token->name;
813       }
814       else if (compound->definitionType()==Definition::TypeGroup)
815       {
816         name=toGroupDef(compound)->groupTitle();
817       }
818       children.append<DocLinkedWord>(
819             this,parent,name,
820             compound->getReference(),
821             compound->getOutputFileBase(),
822             anchor,
823             compound->briefDescriptionAsTooltip());
824     }
825     else if (compound->definitionType()==Definition::TypeFile &&
826              (toFileDef(compound))->generateSourceFile()
827             ) // undocumented file that has source code we can link to
828     {
829       children.append<DocLinkedWord>(
830              this,parent,context.token->name,
831              compound->getReference(),
832              compound->getSourceFileBase(),
833              "",
834              compound->briefDescriptionAsTooltip());
835     }
836     else // not linkable
837     {
838       children.append<DocWord>(this,parent,name);
839     }
840   }
841   else if (!context.insideHtmlLink && len>1 && context.token->name.at(len-1)==':')
842   {
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,":");
848   }
849   else if (!context.insideHtmlLink && (cd=getClass(context.token->name+"-p")))
850   {
851     // special case 2, where the token name is not a class, but could
852     // be a Obj-C protocol
853     children.append<DocLinkedWord>(
854           this,parent,name,
855           cd->getReference(),
856           cd->getOutputFileBase(),
857           cd->anchor(),
858           cd->briefDescriptionAsTooltip());
859   }
860   else // normal non-linkable word
861   {
862     if (context.token->name.startsWith("#") || context.token->name.startsWith("::"))
863     {
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);
866     }
867     else
868     {
869       children.append<DocWord>(this,parent,name);
870     }
871   }
872 }
873
874 void DocParser::handleParameterType(DocNodeVariant *parent,DocNodeList &children,const QCString &paramTypes)
875 {
876   QCString name = context.token->name; // save token name
877   AUTO_TRACE("name={}",name);
878   QCString name1;
879   int p=0,i,ii;
880   while ((i=paramTypes.find('|',p))!=-1)
881   {
882     name1 = paramTypes.mid(p,i-p);
883     ii=name1.find('[');
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
887     p=i+1;
888     children.append<DocSeparator>(this,parent,"|");
889   }
890   name1 = paramTypes.mid(p);
891   ii=name1.find('[');
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
896 }
897
898 void DocParser::handleInternalRef(DocNodeVariant *parent,DocNodeList &children)
899 {
900   int tok=tokenizer.lex();
901   QCString tokenName = context.token->name;
902   AUTO_TRACE("name={}",tokenName);
903   if (tok!=TK_WHITESPACE)
904   {
905     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
906         qPrint(tokenName));
907     return;
908   }
909   tokenizer.setStateInternalRef();
910   tok=tokenizer.lex(); // get the reference id
911   if (tok!=TK_WORD && tok!=TK_LNKWORD)
912   {
913     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
914         DocTokenizer::tokToString(tok),qPrint(tokenName));
915     return;
916   }
917   children.append<DocInternalRef>(this,parent,context.token->name);
918   children.get_last<DocInternalRef>()->parse();
919 }
920
921 void DocParser::handleAnchor(DocNodeVariant *parent,DocNodeList &children)
922 {
923   AUTO_TRACE();
924   int tok=tokenizer.lex();
925   if (tok!=TK_WHITESPACE)
926   {
927     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\%s command",
928         qPrint(context.token->name));
929     return;
930   }
931   tokenizer.setStateAnchor();
932   tok=tokenizer.lex();
933   if (tok==0)
934   {
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));
937     return;
938   }
939   else if (tok!=TK_WORD && tok!=TK_LNKWORD)
940   {
941     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of %s",
942         DocTokenizer::tokToString(tok),qPrint(context.token->name));
943     return;
944   }
945   tokenizer.setStatePara();
946   children.append<DocAnchor>(this,parent,context.token->name,FALSE);
947 }
948
949
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
958  */
959 void DocParser::defaultHandleTitleAndSize(const int cmd, DocNodeVariant *parent, DocNodeList &children, QCString &width,QCString &height)
960 {
961   AUTO_TRACE();
962   auto ns = AutoNodeStack(this,parent);
963
964   // parse title
965   tokenizer.setStateTitle();
966   int tok;
967   while ((tok=tokenizer.lex()))
968   {
969     if (tok==TK_WORD && (context.token->name=="width=" || context.token->name=="height="))
970     {
971       // special case: no title, but we do have a size indicator
972       break;
973     }
974     else if (tok==TK_HTMLTAG)
975     {
976       tokenizer.unputString(context.token->name);
977       break;
978     }
979     if (!defaultHandleToken(parent,tok,children))
980     {
981       errorHandleDefaultToken(parent,tok,children,Mappers::cmdMapper->find(cmd));
982     }
983   }
984   // parse size attributes
985   if (tok == 0)
986   {
987     tok=tokenizer.lex();
988   }
989   while (tok==TK_WHITESPACE || tok==TK_WORD) // there are values following the title
990   {
991     if (tok==TK_WORD)
992     {
993       if (context.token->name=="width=" || context.token->name=="height=")
994       {
995         tokenizer.setStateTitleAttrValue();
996         context.token->name = context.token->name.left(context.token->name.length()-1);
997       }
998
999       if (context.token->name=="width")
1000       {
1001         width = context.token->chars;
1002       }
1003       else if (context.token->name=="height")
1004       {
1005         height = context.token->chars;
1006       }
1007       else
1008       {
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)));
1012         break;
1013       }
1014     }
1015
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)
1020     {
1021       tokenizer.unputString(context.token->name);
1022       tokenizer.unputString(tok==TK_COMMAND_AT ? "@" : "\\");
1023     }
1024     else if (tok==TK_SYMBOL || tok==TK_HTMLTAG)
1025     {
1026       tokenizer.unputString(context.token->name);
1027     }
1028   }
1029   tokenizer.setStatePara();
1030
1031   handlePendingStyleCommands(parent,children);
1032   AUTO_TRACE_EXIT("width={} height={}",width,height);
1033 }
1034
1035 void DocParser::handleImage(DocNodeVariant *parent, DocNodeList &children)
1036 {
1037   AUTO_TRACE();
1038   bool inlineImage = false;
1039   QCString anchorStr;
1040
1041   int tok=tokenizer.lex();
1042   if (tok!=TK_WHITESPACE)
1043   {
1044     if (tok==TK_WORD)
1045     {
1046       if (context.token->name == "{")
1047       {
1048         tokenizer.setStateOptions();
1049         tokenizer.lex();
1050         tokenizer.setStatePara();
1051         StringVector optList=split(context.token->name.str(),",");
1052         for (const auto &opt : optList)
1053         {
1054           if (opt.empty()) continue;
1055           QCString locOpt(opt);
1056           QCString locOptLow;
1057           locOpt = locOpt.stripWhiteSpace();
1058           locOptLow = locOpt.lower();
1059           if (locOptLow == "inline")
1060           {
1061             inlineImage = true;
1062           }
1063           else if (locOptLow.startsWith("anchor:"))
1064           {
1065             if (!anchorStr.isEmpty())
1066             {
1067                warn_doc_error(context.fileName,tokenizer.getLineNr(),
1068                   "multiple use of option 'anchor' for 'image' command, ignoring: '%s'",
1069                   qPrint(locOpt.mid(7)));
1070             }
1071             else
1072             {
1073                anchorStr = locOpt.mid(7);
1074             }
1075           }
1076           else
1077           {
1078             warn_doc_error(context.fileName,tokenizer.getLineNr(),
1079                   "unknown option '%s' for 'image' command specified",
1080                   qPrint(locOpt));
1081           }
1082         }
1083         tok=tokenizer.lex();
1084         if (tok!=TK_WHITESPACE)
1085         {
1086           warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1087           return;
1088         }
1089       }
1090     }
1091     else
1092     {
1093       warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1094       return;
1095     }
1096   }
1097   tok=tokenizer.lex();
1098   if (tok!=TK_WORD && tok!=TK_LNKWORD)
1099   {
1100     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1101         DocTokenizer::tokToString(tok));
1102     return;
1103   }
1104   tok=tokenizer.lex();
1105   if (tok!=TK_WHITESPACE)
1106   {
1107     warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\image command");
1108     return;
1109   }
1110   DocImage::Type t;
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;
1117   else
1118   {
1119     warn_doc_error(context.fileName,tokenizer.getLineNr(),"output format `%s` specified as the first argument of "
1120         "\\image command is not valid",
1121         qPrint(imgType));
1122     return;
1123   }
1124   tokenizer.setStateFile();
1125   tok=tokenizer.lex();
1126   tokenizer.setStatePara();
1127   if (tok!=TK_WORD)
1128   {
1129     warn_doc_error(context.fileName,tokenizer.getLineNr(),"unexpected token %s as the argument of \\image",
1130         DocTokenizer::tokToString(tok));
1131     return;
1132   }
1133   if (!anchorStr.isEmpty())
1134   {
1135     children.append<DocAnchor>(this,parent,anchorStr,true);
1136   }
1137   HtmlAttribList attrList;
1138   children.append<DocImage>(this,parent,attrList,
1139                  findAndCopyImage(context.token->name,t),t,"",inlineImage);
1140   children.get_last<DocImage>()->parse();
1141 }
1142
1143
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.
1154  */
1155 bool DocParser::defaultHandleToken(DocNodeVariant *parent,int tok, DocNodeList &children,bool handleWord)
1156 {
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
1160      )
1161   {
1162   }
1163 reparsetoken:
1164   QCString tokenName = context.token->name;
1165   AUTO_TRACE_ADD("tokenName={}",tokenName);
1166   switch (tok)
1167   {
1168     case TK_COMMAND_AT:
1169         // fall through
1170     case TK_COMMAND_BS:
1171       switch (Mappers::cmdMapper->map(tokenName))
1172       {
1173         case CMD_BSLASH:
1174           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_BSlash);
1175           break;
1176         case CMD_AT:
1177           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_At);
1178           break;
1179         case CMD_LESS:
1180           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Less);
1181           break;
1182         case CMD_GREATER:
1183           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Greater);
1184           break;
1185         case CMD_AMP:
1186           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Amp);
1187           break;
1188         case CMD_DOLLAR:
1189           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dollar);
1190           break;
1191         case CMD_HASH:
1192           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Hash);
1193           break;
1194         case CMD_DCOLON:
1195           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_DoubleColon);
1196           break;
1197         case CMD_PERCENT:
1198           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Percent);
1199           break;
1200         case CMD_NDASH:
1201           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1202           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1203           break;
1204         case CMD_MDASH:
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);
1208           break;
1209         case CMD_QUOTE:
1210           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Quot);
1211           break;
1212         case CMD_PUNT:
1213           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Dot);
1214           break;
1215         case CMD_PLUS:
1216           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Plus);
1217           break;
1218         case CMD_MINUS:
1219           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Minus);
1220           break;
1221         case CMD_EQUAL:
1222           children.append<DocSymbol>(this,parent,HtmlEntityMapper::Sym_Equal);
1223           break;
1224         case CMD_EMPHASIS:
1225           {
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)
1232             {
1233               AUTO_TRACE_ADD("CMD_EMPHASIS: reparsing");
1234               goto reparsetoken;
1235             }
1236           }
1237           break;
1238         case CMD_BOLD:
1239           {
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)
1246             {
1247               AUTO_TRACE_ADD("CMD_BOLD: reparsing");
1248               goto reparsetoken;
1249             }
1250           }
1251           break;
1252         case CMD_CODE:
1253           {
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)
1260             {
1261               AUTO_TRACE_ADD("CMD_CODE: reparsing");
1262               goto reparsetoken;
1263             }
1264           }
1265           break;
1266         case CMD_HTMLONLY:
1267           {
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();
1273           }
1274           break;
1275         case CMD_MANONLY:
1276           {
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();
1282           }
1283           break;
1284         case CMD_RTFONLY:
1285           {
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();
1291           }
1292           break;
1293         case CMD_LATEXONLY:
1294           {
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();
1300           }
1301           break;
1302         case CMD_XMLONLY:
1303           {
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();
1309           }
1310           break;
1311         case CMD_DBONLY:
1312           {
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();
1318           }
1319           break;
1320         case CMD_FORMULA:
1321           {
1322             children.append<DocFormula>(this,parent,context.token->id);
1323           }
1324           break;
1325         case CMD_ANCHOR:
1326         case CMD_IANCHOR:
1327           {
1328             handleAnchor(parent,children);
1329           }
1330           break;
1331         case CMD_INTERNALREF:
1332           {
1333             handleInternalRef(parent,children);
1334             tokenizer.setStatePara();
1335           }
1336           break;
1337         case CMD_SETSCOPE:
1338           {
1339             QCString scope;
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();
1346           }
1347           break;
1348         case CMD_IMAGE:
1349           handleImage(parent,children);
1350           break;
1351         default:
1352           return FALSE;
1353       }
1354       break;
1355     case TK_HTMLTAG:
1356       {
1357         switch (Mappers::htmlTagMapper->map(tokenName))
1358         {
1359           case HTML_DIV:
1360             warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <div> tag in heading");
1361             break;
1362           case HTML_PRE:
1363             warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <pre> tag in heading");
1364             break;
1365           case HTML_BOLD:
1366             if (!context.token->endTag)
1367             {
1368               handleStyleEnter(parent,children,DocStyleChange::Bold,tokenName,&context.token->attribs);
1369             }
1370             else
1371             {
1372               handleStyleLeave(parent,children,DocStyleChange::Bold,tokenName);
1373             }
1374             break;
1375           case HTML_S:
1376             if (!context.token->endTag)
1377             {
1378               handleStyleEnter(parent,children,DocStyleChange::S,tokenName,&context.token->attribs);
1379             }
1380             else
1381             {
1382               handleStyleLeave(parent,children,DocStyleChange::S,tokenName);
1383             }
1384             break;
1385           case HTML_STRIKE:
1386             if (!context.token->endTag)
1387             {
1388               handleStyleEnter(parent,children,DocStyleChange::Strike,tokenName,&context.token->attribs);
1389             }
1390             else
1391             {
1392               handleStyleLeave(parent,children,DocStyleChange::Strike,tokenName);
1393             }
1394             break;
1395           case HTML_DEL:
1396             if (!context.token->endTag)
1397             {
1398               handleStyleEnter(parent,children,DocStyleChange::Del,tokenName,&context.token->attribs);
1399             }
1400             else
1401             {
1402               handleStyleLeave(parent,children,DocStyleChange::Del,tokenName);
1403             }
1404             break;
1405           case HTML_UNDERLINE:
1406             if (!context.token->endTag)
1407             {
1408               handleStyleEnter(parent,children,DocStyleChange::Underline,tokenName,&context.token->attribs);
1409             }
1410             else
1411             {
1412               handleStyleLeave(parent,children,DocStyleChange::Underline,tokenName);
1413             }
1414             break;
1415           case HTML_INS:
1416             if (!context.token->endTag)
1417             {
1418               handleStyleEnter(parent,children,DocStyleChange::Ins,tokenName,&context.token->attribs);
1419             }
1420             else
1421             {
1422               handleStyleLeave(parent,children,DocStyleChange::Ins,tokenName);
1423             }
1424             break;
1425           case HTML_CODE:
1426           case XML_C:
1427             if (!context.token->endTag)
1428             {
1429               handleStyleEnter(parent,children,DocStyleChange::Code,tokenName,&context.token->attribs);
1430             }
1431             else
1432             {
1433               handleStyleLeave(parent,children,DocStyleChange::Code,tokenName);
1434             }
1435             break;
1436           case HTML_EMPHASIS:
1437             if (!context.token->endTag)
1438             {
1439               handleStyleEnter(parent,children,DocStyleChange::Italic,tokenName,&context.token->attribs);
1440             }
1441             else
1442             {
1443               handleStyleLeave(parent,children,DocStyleChange::Italic,tokenName);
1444             }
1445             break;
1446           case HTML_SUB:
1447             if (!context.token->endTag)
1448             {
1449               handleStyleEnter(parent,children,DocStyleChange::Subscript,tokenName,&context.token->attribs);
1450             }
1451             else
1452             {
1453               handleStyleLeave(parent,children,DocStyleChange::Subscript,tokenName);
1454             }
1455             break;
1456           case HTML_SUP:
1457             if (!context.token->endTag)
1458             {
1459               handleStyleEnter(parent,children,DocStyleChange::Superscript,tokenName,&context.token->attribs);
1460             }
1461             else
1462             {
1463               handleStyleLeave(parent,children,DocStyleChange::Superscript,tokenName);
1464             }
1465             break;
1466           case HTML_CENTER:
1467             if (!context.token->endTag)
1468             {
1469               handleStyleEnter(parent,children,DocStyleChange::Center,tokenName,&context.token->attribs);
1470             }
1471             else
1472             {
1473               handleStyleLeave(parent,children,DocStyleChange::Center,tokenName);
1474             }
1475             break;
1476           case HTML_SMALL:
1477             if (!context.token->endTag)
1478             {
1479               handleStyleEnter(parent,children,DocStyleChange::Small,tokenName,&context.token->attribs);
1480             }
1481             else
1482             {
1483               handleStyleLeave(parent,children,DocStyleChange::Small,tokenName);
1484             }
1485             break;
1486           case HTML_CITE:
1487             if (!context.token->endTag)
1488             {
1489               handleStyleEnter(parent,children,DocStyleChange::Cite,tokenName,&context.token->attribs);
1490             }
1491             else
1492             {
1493               handleStyleLeave(parent,children,DocStyleChange::Cite,tokenName);
1494             }
1495             break;
1496           case HTML_IMG:
1497             if (!context.token->endTag)
1498               handleImg(parent,children,context.token->attribs);
1499             break;
1500           default:
1501             return FALSE;
1502             break;
1503         }
1504       }
1505       break;
1506     case TK_SYMBOL:
1507       {
1508         HtmlEntityMapper::SymType s = DocSymbol::decodeSymbol(tokenName);
1509         if (s!=HtmlEntityMapper::Sym_Unknown)
1510         {
1511           children.append<DocSymbol>(this,parent,s);
1512         }
1513         else
1514         {
1515           return FALSE;
1516         }
1517       }
1518       break;
1519     case TK_WHITESPACE:
1520     case TK_NEWPARA:
1521 handlepara:
1522       if (insidePRE(parent) || !children.empty())
1523       {
1524         children.append<DocWhiteSpace>(this,parent,context.token->chars);
1525       }
1526       break;
1527     case TK_LNKWORD:
1528       if (handleWord)
1529       {
1530         handleLinkedWord(parent,children);
1531       }
1532       else
1533         return FALSE;
1534       break;
1535     case TK_WORD:
1536       if (handleWord)
1537       {
1538         children.append<DocWord>(this,parent,context.token->name);
1539       }
1540       else
1541         return FALSE;
1542       break;
1543     case TK_URL:
1544       if (context.insideHtmlLink)
1545       {
1546         children.append<DocWord>(this,parent,context.token->name);
1547       }
1548       else
1549       {
1550         children.append<DocURL>(this,parent,context.token->name,context.token->isEMailAddr);
1551       }
1552       break;
1553     default:
1554       return FALSE;
1555   }
1556   return TRUE;
1557 }
1558
1559 //---------------------------------------------------------------------------
1560
1561 void DocParser::handleImg(DocNodeVariant *parent, DocNodeList &children,const HtmlAttribList &tagHtmlAttribs)
1562 {
1563   AUTO_TRACE();
1564   bool found=FALSE;
1565   uint32_t index=0;
1566   for (const auto &opt : tagHtmlAttribs)
1567   {
1568     AUTO_TRACE_ADD("option name={} value='{}'",opt.name,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!");
1587   }
1588 }
1589
1590 //---------------------------------------------------------------------------
1591
1592 int DocParser::internalValidatingParseDoc(DocNodeVariant *parent,DocNodeList &children,
1593                                     const QCString &doc)
1594 {
1595   AUTO_TRACE();
1596   int retval = RetVal_OK;
1597
1598   if (doc.isEmpty()) return retval;
1599
1600   tokenizer.init(doc.data(),context.fileName,context.markdownSupport,context.insideHtmlLink);
1601
1602   // first parse any number of paragraphs
1603   bool isFirst=TRUE;
1604   DocPara *lastPar=!children.empty() ? std::get_if<DocPara>(&children.back()): 0;
1605   if (lastPar)
1606   { // last child item was a paragraph
1607     isFirst=FALSE;
1608   }
1609   do
1610   {
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())
1616     {
1617       if (lastPar) lastPar->markLast(FALSE);
1618       lastPar=par;
1619     }
1620     else
1621     {
1622       children.pop_back();
1623     }
1624   } while (retval==TK_NEWPARA);
1625   if (lastPar) lastPar->markLast();
1626
1627   AUTO_TRACE_EXIT("isFirst={} isLast={}",lastPar?lastPar->isFirst():-1,lastPar?lastPar->isLast():-1);
1628   return retval;
1629 }
1630
1631 //---------------------------------------------------------------------------
1632
1633 void DocParser::readTextFileByName(const QCString &file,QCString &text)
1634 {
1635   AUTO_TRACE("file={} text={}",file,text);
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, uint32_t &j, size_t len)
1681 {
1682   uint32_t 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   uint32_t 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 uint32_t isCopyBriefOrDetailsCmd(const char *data, uint32_t i,size_t len,bool &brief)
1744 {
1745   uint32_t 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 uint32_t isVerbatimSection(const char *data,uint32_t i,size_t len,QCString &endMarker)
1755 {
1756   uint32_t 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 uint32_t skipToEndMarker(const char *data,uint32_t i,size_t 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 : static_cast<uint32_t>(len);
1794 }
1795
1796
1797 QCString DocParser::processCopyDoc(const char *data,size_t &len)
1798 {
1799   AUTO_TRACE("data={} len={}",Trace::trunc(data),len);
1800   GrowBuf buf;
1801   uint32_t 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       uint32_t 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             auto addDocs = [&](const QCString &file_,int line_,const QCString &doc_)
1828             {
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_));
1833             };
1834             if (isBrief)
1835             {
1836               addDocs(def->briefFile(),def->briefLine(),brief);
1837             }
1838             else
1839             {
1840               addDocs(def->docFile(),def->docLine(),doc);
1841               if (def->definitionType()==Definition::TypeMember)
1842               {
1843                 const MemberDef *md = toMemberDef(def);
1844                 const ArgumentList &docArgList = md->templateMaster() ?
1845                     md->templateMaster()->argumentList() :
1846                     md->argumentList();
1847                 buf.addStr(inlineArgListToDoc(docArgList));
1848               }
1849             }
1850             context.copyStack.pop_back();
1851             buf.addStr(" \\ilinebr\\ifile \""+context.fileName+"\" ");
1852             buf.addStr("\\iline "+QCString().setNum(lineNr)+" ");
1853           }
1854           else
1855           {
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));
1859           }
1860         }
1861         else
1862         {
1863           warn_doc_error(context.fileName,tokenizer.getLineNr(),
1864                "@copy%s or @copydoc target '%s' not found", isBrief?"brief":"details",
1865                qPrint(id));
1866         }
1867         // skip over command
1868         i=j;
1869       }
1870       else
1871       {
1872         QCString endMarker;
1873         uint32_t k = isVerbatimSection(data,i,len,endMarker);
1874         if (k>0)
1875         {
1876           uint32_t orgPos = i;
1877           i=skipToEndMarker(data,k,len,endMarker);
1878           buf.addStr(data+orgPos,i-orgPos);
1879           // TODO: adjust lineNr
1880         }
1881         else
1882         {
1883           buf.addChar(c);
1884           i++;
1885         }
1886       }
1887     }
1888     else // not a command, just copy
1889     {
1890       buf.addChar(c);
1891       i++;
1892       lineNr += (c=='\n') ? 1 : 0;
1893     }
1894   }
1895   len = static_cast<uint32_t>(buf.getPos());
1896   buf.addChar(0);
1897   AUTO_TRACE_EXIT("result={}",Trace::trunc(buf.get()));
1898   return buf.get();
1899 }
1900
1901
1902 //---------------------------------------------------------------------------
1903
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)
1911 {
1912   DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
1913   assert(parser!=0);
1914   if (parser==0) return 0;
1915   //printf("validatingParseDoc(%s,%s)=[%s]\n",ctx?qPrint(ctx->name()):"<none>",
1916   //                                     md?qPrint(md->name()):"<none>",
1917   //                                     input);
1918   //printf("========== validating %s at line %d\n",qPrint(fileName),startLine);
1919   //printf("---------------- input --------------------\n%s\n----------- end input -------------------\n",qPrint(input));
1920
1921   // set initial token
1922   parser->context.token = parser->tokenizer.resetToken();
1923
1924   if (ctx && ctx!=Doxygen::globalScope &&
1925       (ctx->definitionType()==Definition::TypeClass ||
1926        ctx->definitionType()==Definition::TypeNamespace
1927       )
1928      )
1929   {
1930     parser->context.context = ctx->qualifiedName();
1931   }
1932   else if (ctx && ctx->definitionType()==Definition::TypePage)
1933   {
1934     const Definition *scope = (toPageDef(ctx))->getPageScope();
1935     if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1936   }
1937   else if (ctx && ctx->definitionType()==Definition::TypeGroup)
1938   {
1939     const Definition *scope = (toGroupDef(ctx))->getGroupScope();
1940     if (scope && scope!=Doxygen::globalScope) parser->context.context = scope->name();
1941   }
1942   else
1943   {
1944     parser->context.context = "";
1945   }
1946   parser->context.scope = ctx;
1947
1948   if (indexWords && Doxygen::searchIndex)
1949   {
1950     if (md)
1951     {
1952       parser->context.searchUrl=md->getOutputFileBase();
1953       Doxygen::searchIndex->setCurrentDoc(md,md->anchor(),false);
1954     }
1955     else if (ctx)
1956     {
1957       parser->context.searchUrl=ctx->getOutputFileBase();
1958       Doxygen::searchIndex->setCurrentDoc(ctx,ctx->anchor(),false);
1959     }
1960   }
1961   else
1962   {
1963     parser->context.searchUrl="";
1964   }
1965
1966   parser->context.fileName = fileName;
1967   parser->context.relPath = (!linkFromIndex && ctx) ?
1968                QCString(relativePathToRoot(ctx->getOutputFileBase())) :
1969                QCString("");
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;
1988
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')
1994   {
1995     inpStr+='\n';
1996   }
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);
2000
2001   // build abstract syntax tree
2002   auto ast = std::make_unique<DocNodeAST>(DocRoot(parser,md!=0,singleLine));
2003   std::get<DocRoot>(ast->root).parse();
2004
2005   if (Debug::isFlagSet(Debug::PrintTree))
2006   {
2007     // pretty print the result
2008     std::visit(PrintDocVisitor{},ast->root);
2009   }
2010
2011   if (md && md->isFunction())
2012   {
2013     parser->checkUnOrMultipleDocumentedParams();
2014   }
2015   if (parser->context.memberDef) parser->context.memberDef->detectUndocumentedParams(parser->context.hasParamCommand,parser->context.hasReturnCommand);
2016
2017   // reset token
2018   parser->tokenizer.resetToken();
2019
2020   //printf(">>>>>> end validatingParseDoc(%s,%s)\n",ctx?qPrint(ctx->name()):"<none>",
2021   //                                     md?qPrint(md->name()):"<none>");
2022
2023   return ast;
2024 }
2025
2026 IDocNodeASTPtr validatingParseText(IDocParser &parserIntf,const QCString &input)
2027 {
2028   DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2029   assert(parser!=0);
2030   if (parser==0) return 0;
2031
2032   // set initial token
2033   parser->context.token = parser->tokenizer.resetToken();
2034
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);
2059
2060
2061   auto ast = std::make_unique<DocNodeAST>(DocText(parser));
2062
2063   if (!input.isEmpty())
2064   {
2065     parser->tokenizer.setLineNr(1);
2066     parser->tokenizer.init(input.data(),parser->context.fileName,
2067                            parser->context.markdownSupport,parser->context.insideHtmlLink);
2068
2069     // build abstract syntax tree
2070     std::get<DocText>(ast->root).parse();
2071
2072     if (Debug::isFlagSet(Debug::PrintTree))
2073     {
2074       // pretty print the result
2075       std::visit(PrintDocVisitor{},ast->root);
2076     }
2077   }
2078
2079   return ast;
2080 }
2081
2082 IDocNodeASTPtr createRef(IDocParser &parserIntf,const QCString &target,const QCString &context)
2083 {
2084   DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2085   assert(parser!=0);
2086   if (parser==0) return 0;
2087   return std::make_unique<DocNodeAST>(DocRef(parser,0,target,context));
2088 }
2089
2090 void docFindSections(const QCString &input,
2091                      const Definition *d,
2092                      const QCString &fileName)
2093 {
2094   DocParser parser;
2095   parser.tokenizer.findSections(input,d,fileName);
2096 }
2097