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