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