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;
98 //printf("Search for %s\n",fileName);
99 if (fd)
100 {
101 if (ambig & doWarn)
102 {
103 warn_doc_error(context.fileName,tokenizer.getLineNr(),
104 "image file name '{}' is ambiguous.\n"
105 "Possible candidates:\n{}", fileName,showFileDefMatches(Doxygen::imageNameLinkedMap,fileName));
106 }
107
108 QCString inputFile = fd->absFilePath();
109 FileInfo infi(inputFile.str());
110 if (infi.exists())
111 {
112 result = fileName;
113 int i = result.findRev('/');
114 if (i!=-1 || (i=result.findRev('\\'))!=-1)
115 {
116 result = result.right(static_cast<int>(result.length())-i-1);
117 }
118 //printf("fileName=%s result=%s\n",fileName,qPrint(result));
119 QCString outputDir;
120 switch(type)
121 {
122 case DocImage::Html:
123 if (!Config_getBool(GENERATE_HTML)) return result;
124 outputDir = Config_getString(HTML_OUTPUT);
125 break;
126 case DocImage::Latex:
127 if (!Config_getBool(GENERATE_LATEX)) return result;
128 outputDir = Config_getString(LATEX_OUTPUT);
129 break;
131 if (!Config_getBool(GENERATE_DOCBOOK)) return result;
132 outputDir = Config_getString(DOCBOOK_OUTPUT);
133 break;
134 case DocImage::Rtf:
135 if (!Config_getBool(GENERATE_RTF)) return result;
136 outputDir = Config_getString(RTF_OUTPUT);
137 break;
138 case DocImage::Xml:
139 if (!Config_getBool(GENERATE_XML)) return result;
140 outputDir = Config_getString(XML_OUTPUT);
141 break;
142 }
143 QCString outputFile = outputDir+"/"+result;
144 FileInfo outfi(outputFile.str());
145 if (outfi.isSymLink())
146 {
147 Dir().remove(outputFile.str());
148 warn_doc_error(context.fileName,tokenizer.getLineNr(),
149 "destination of image {} is a symlink, replacing with image",
150 outputFile);
151 }
152 if (outputFile!=inputFile) // prevent copying to ourself
153 {
154 if (copyFile(inputFile,outputFile) && type==DocImage::Html)
155 {
156 Doxygen::indexList->addImageFile(result);
157 }
158 }
159 }
160 else
161 {
162 warn_doc_error(context.fileName,tokenizer.getLineNr(),
163 "could not open image {}",fileName);
164 }
165
166 if (type==DocImage::Latex && Config_getBool(USE_PDFLATEX) &&
167 fd->name().endsWith(".eps")
168 )
169 { // we have an .eps image in pdflatex mode => convert it to a pdf.
170 QCString outputDir = Config_getString(LATEX_OUTPUT);
171 QCString baseName = fd->name().left(fd->name().length()-4);
172 QCString epstopdfArgs(4096, QCString::ExplicitSize);
173 epstopdfArgs.sprintf("\"%s/%s.eps\" --outfile=\"%s/%s.pdf\"",
174 qPrint(outputDir), qPrint(baseName),
175 qPrint(outputDir), qPrint(baseName));
176 if (Portable::system("epstopdf",epstopdfArgs)!=0)
177 {
178 err("Problems running epstopdf. Check your TeX installation!\n");
179 }
180 else
181 {
182 Dir().remove(outputDir.str()+"/"+baseName.str()+".eps");
183 }
184 return baseName;
185 }
186 }
187 else
188 {
189 result=fileName;
190 if (!result.startsWith("http:") && !result.startsWith("https:") && doWarn)
191 {
192 warn_doc_error(context.fileName,tokenizer.getLineNr(),
193 "image file {} is not found in IMAGE_PATH: "
194 "assuming external image.",fileName
195 );
196 }
197 }
198 return result;
199}
200
201/*! Collects the parameters found with \@param command
202 * in a list context.paramsFound. If
203 * the parameter is not an actual parameter of the current
204 * member context.memberDef, then a warning is raised (unless warnings
205 * are disabled altogether).
206 */
208{
209 if (!(Config_getBool(WARN_IF_DOC_ERROR) || Config_getBool(WARN_IF_INCOMPLETE_DOC))) return;
210 if (context.memberDef==nullptr) return; // not a member
211 std::string name = context.token->name.str();
212 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
213 context.memberDef->argumentList() :
214 context.memberDef->declArgumentList();
215 SrcLangExt lang = context.memberDef->getLanguage();
216 //printf("isDocsForDefinition()=%d\n",context.memberDef->isDocsForDefinition());
217 if (al.empty()) return; // no argument list
218
219 static const reg::Ex re(R"(\$?\w+\.*)");
220 reg::Iterator it(name,re);
222 for (; it!=end ; ++it)
223 {
224 const auto &match = *it;
225 QCString aName=match.str();
226 if (lang==SrcLangExt::Fortran) aName=aName.lower();
227 //printf("aName='%s'\n",qPrint(aName));
228 bool found=FALSE;
229 for (const Argument &a : al)
230 {
231 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
232 if (lang==SrcLangExt::Fortran) argName=argName.lower();
233 argName=argName.stripWhiteSpace();
234 //printf("argName='%s' aName=%s\n",qPrint(argName),qPrint(aName));
235 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
236 if (aName==argName)
237 {
238 context.paramsFound.insert(aName.str());
239 found=TRUE;
240 break;
241 }
242 }
243 if (!found)
244 {
245 //printf("member type=%d\n",context.memberDef->memberType());
246 QCString scope=context.memberDef->getScopeString();
247 if (!scope.isEmpty()) scope+="::"; else scope="";
248 QCString inheritedFrom = "";
249 QCString docFile = context.memberDef->docFile();
250 int docLine = context.memberDef->docLine();
251 const MemberDef *inheritedMd = context.memberDef->inheritsDocsFrom();
252 if (inheritedMd) // documentation was inherited
253 {
254 inheritedFrom.sprintf(" inherited from member %s at line "
255 "%d in file %s",qPrint(inheritedMd->name()),
256 inheritedMd->docLine(),qPrint(inheritedMd->docFile()));
257 docFile = context.memberDef->getDefFileName();
258 docLine = context.memberDef->getDefLine();
259 }
260 QCString alStr = argListToString(al);
261 warn_doc_error(docFile,docLine,
262 "argument '{}' of command @param "
263 "is not found in the argument list of {}{}{}{}",
264 aName, scope, context.memberDef->name(),
265 alStr, inheritedFrom);
266 }
267 }
268}
269/*! Collects the return values found with \@retval command
270 * in a global list g_parserContext.retvalsFound.
271 */
273{
274 QCString name = context.token->name;
275 if (!Config_getBool(WARN_IF_DOC_ERROR)) return;
276 if (context.memberDef==nullptr || name.isEmpty()) return; // not a member or no valid name
277 if (context.retvalsFound.count(name.str())==1) // only report the first double entry
278 {
279 warn_doc_error(context.memberDef->getDefFileName(),
280 context.memberDef->getDefLine(),
281 "return value '{}' of {} has multiple documentation sections",
282 name, context.memberDef->qualifiedName());
283 }
284 context.retvalsFound.insert(name.str());
285}
286
287/*! Checks if the parameters that have been specified using \@param are
288 * indeed all parameters and that a parameter does not have multiple
289 * \@param blocks.
290 * Must be called after checkArgumentName() has been called for each
291 * argument.
292 */
294{
295 if (context.memberDef && context.hasParamCommand)
296 {
297 const ArgumentList &al=context.memberDef->isDocsForDefinition() ?
298 context.memberDef->argumentList() :
299 context.memberDef->declArgumentList();
300 SrcLangExt lang = context.memberDef->getLanguage();
301 if (!al.empty())
302 {
303 ArgumentList undocParams;
304 for (const Argument &a: al)
305 {
306 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
307 if (lang==SrcLangExt::Fortran) argName = argName.lower();
308 argName=argName.stripWhiteSpace();
309 QCString aName = argName;
310 if (argName.endsWith("...")) argName=argName.left(argName.length()-3);
311 if (lang==SrcLangExt::Python && (argName=="self" || argName=="cls"))
312 {
313 // allow undocumented self / cls parameter for Python
314 }
315 else if (lang==SrcLangExt::Cpp && (a.type=="this" || a.type.startsWith("this ")))
316 {
317 // allow undocumented this (for C++23 deducing this), see issue #11123
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 "argument {} from the argument list of {} has multiple @param documentation sections",
331 aName, context.memberDef->qualifiedName());
332 }
333 }
334 }
335 if (!undocParams.empty() && Config_getBool(WARN_IF_INCOMPLETE_DOC))
336 {
337 bool first=TRUE;
338 QCString errMsg = "The following parameter";
339 if (undocParams.size()>1) errMsg+="s";
340 errMsg+=" of "+
341 QCString(context.memberDef->qualifiedName()) +
343 (undocParams.size()>1 ? " are" : " is") + " not documented:\n";
344 for (const Argument &a : undocParams)
345 {
346 QCString argName = context.memberDef->isDefine() ? a.type : a.name;
347 if (lang==SrcLangExt::Fortran) argName = argName.lower();
348 argName=argName.stripWhiteSpace();
349 if (!first) errMsg+="\n";
350 first=FALSE;
351 errMsg+=" parameter '"+argName+"'";
352 }
353 warn_incomplete_doc(context.memberDef->docFile(), context.memberDef->docLine(), "{}", errMsg);
354 }
355 }
356 else
357 {
358 if (context.paramsFound.empty() && Config_getBool(WARN_IF_DOC_ERROR))
359 {
360 warn_doc_error(context.memberDef->docFile(),
361 context.memberDef->docLine(),
362 "{} has @param documentation sections but no arguments",
363 context.memberDef->qualifiedName());
364 }
365 }
366 }
367}
368
369
370//---------------------------------------------------------------------------
371
372//---------------------------------------------------------------------------
373
374//---------------------------------------------------------------------------
375/*! Looks for a documentation block with name commandName in the current
376 * context (g_parserContext.context). The resulting documentation string is
377 * put in pDoc, the definition in which the documentation was found is
378 * put in pDef.
379 * @retval TRUE if name was found.
380 * @retval FALSE if name was not found.
381 */
383 QCString *pDoc,
384 QCString *pBrief,
385 const Definition **pDef)
386{
387 AUTO_TRACE("commandName={}",commandName);
388 *pDoc="";
389 *pBrief="";
390 *pDef=nullptr;
391 QCString cmdArg=commandName;
392 if (cmdArg.isEmpty())
393 {
394 AUTO_TRACE_EXIT("empty");
395 return false;
396 }
397
398 const FileDef *fd=nullptr;
399 const GroupDef *gd=nullptr;
400 const PageDef *pd=nullptr;
401 gd = Doxygen::groupLinkedMap->find(cmdArg);
402 if (gd) // group
403 {
404 *pDoc=gd->documentation();
405 *pBrief=gd->briefDescription();
406 *pDef=gd;
407 AUTO_TRACE_EXIT("group");
408 return true;
409 }
410 pd = Doxygen::pageLinkedMap->find(cmdArg);
411 if (pd) // page
412 {
413 *pDoc=pd->documentation();
414 *pBrief=pd->briefDescription();
415 *pDef=pd;
416 AUTO_TRACE_EXIT("page");
417 return true;
418 }
419 bool ambig = false;
420 fd = findFileDef(Doxygen::inputNameLinkedMap,cmdArg,ambig);
421 if (fd && !ambig) // file
422 {
423 *pDoc=fd->documentation();
424 *pBrief=fd->briefDescription();
425 *pDef=fd;
426 AUTO_TRACE_EXIT("file");
427 return true;
428 }
429
430 // for symbols we need to normalize the separator, so A#B, or A\B, or A.B becomes A::B
431 cmdArg = substitute(cmdArg,"#","::");
432 cmdArg = substitute(cmdArg,"\\","::");
433 bool extractAnonNs = Config_getBool(EXTRACT_ANON_NSPACES);
434 if (extractAnonNs &&
435 cmdArg.startsWith("anonymous_namespace{")
436 )
437 {
438 size_t rightBracePos = cmdArg.find("}", static_cast<int>(qstrlen("anonymous_namespace{")));
439 QCString leftPart = cmdArg.left(rightBracePos + 1);
440 QCString rightPart = cmdArg.right(cmdArg.size() - rightBracePos - 1);
441 rightPart = substitute(rightPart, ".", "::");
442 cmdArg = leftPart + rightPart;
443 }
444 else
445 {
446 cmdArg = substitute(cmdArg,".","::");
447 }
448
449 int l=static_cast<int>(cmdArg.length());
450
451 int funcStart=cmdArg.find('(');
452 if (funcStart==-1)
453 {
454 funcStart=l;
455 }
456 else
457 {
458 // Check for the case of operator() and the like.
459 // beware of scenarios like operator()((foo)bar)
460 int secondParen = cmdArg.find('(', funcStart+1);
461 int leftParen = cmdArg.find(')', funcStart+1);
462 if (leftParen!=-1 && secondParen!=-1)
463 {
464 if (leftParen<secondParen)
465 {
466 funcStart=secondParen;
467 }
468 }
469 }
470
471 QCString name=removeRedundantWhiteSpace(cmdArg.left(funcStart));
472 QCString args=cmdArg.right(l-funcStart);
473 // try if the link is to a member
474 GetDefInput input(
475 context.context.find('.')==-1 ? context.context : QCString(), // find('.') is a hack to detect files
476 name,
477 args);
478 input.checkCV=true;
479 GetDefResult result = getDefs(input);
480 //printf("found=%d context=%s name=%s\n",result.found,qPrint(context.context),qPrint(name));
481 if (result.found && result.md)
482 {
483 *pDoc=result.md->documentation();
484 *pBrief=result.md->briefDescription();
485 *pDef=result.md;
486 AUTO_TRACE_EXIT("member");
487 return true;
488 }
489
490 int scopeOffset=static_cast<int>(context.context.length());
491 do // for each scope
492 {
493 QCString fullName=cmdArg;
494 if (scopeOffset>0)
495 {
496 fullName.prepend(context.context.left(scopeOffset)+"::");
497 }
498 //printf("Trying fullName='%s'\n",qPrint(fullName));
499
500 // try class, namespace, group, page, file reference
501 const ClassDef *cd = Doxygen::classLinkedMap->find(fullName);
502 if (cd) // class
503 {
504 *pDoc=cd->documentation();
505 *pBrief=cd->briefDescription();
506 *pDef=cd;
507 AUTO_TRACE_EXIT("class");
508 return true;
509 }
510 const NamespaceDef *nd = Doxygen::namespaceLinkedMap->find(fullName);
511 if (nd) // namespace
512 {
513 *pDoc=nd->documentation();
514 *pBrief=nd->briefDescription();
515 *pDef=nd;
516 AUTO_TRACE_EXIT("namespace");
517 return true;
518 }
519 if (scopeOffset==0)
520 {
521 scopeOffset=-1;
522 }
523 else
524 {
525 scopeOffset = context.context.findRev("::",scopeOffset-1);
526 if (scopeOffset==-1) scopeOffset=0;
527 }
528 } while (scopeOffset>=0);
529
530 AUTO_TRACE_EXIT("not found");
531 return FALSE;
532}
533
534//---------------------------------------------------------------------------
536 DocNodeList &children,const QCString &txt)
537{
538 switch (tok.value())
539 {
540 case TokenRetval::TK_COMMAND_AT:
541 // fall through
542 case TokenRetval::TK_COMMAND_BS:
543 {
544 char cs[2] = { tok.command_to_char(), 0 };
545 children.append<DocWord>(this,parent,cs + context.token->name);
546 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Illegal command '{:c}{}' found as part of a {}",
547 tok.command_to_char(),context.token->name,txt);
548 }
549 break;
550 case TokenRetval::TK_SYMBOL:
551 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported symbol '{}' found as part of a {}",
552 qPrint(context.token->name), qPrint(txt));
553 break;
554 case TokenRetval::TK_HTMLTAG:
555 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unsupported HTML tag <{}{}> found as part of a {}",
556 context.token->endTag ? "/" : "",context.token->name, txt);
557 break;
558 default:
559 children.append<DocWord>(this,parent,context.token->name);
560 warn_doc_error(context.fileName,tokenizer.getLineNr(),"Unexpected token {} found as part of a {}",
561 tok.to_string(), txt);
562 break;
563 }
564}
565
566//---------------------------------------------------------------------------
567
569{
570 AUTO_TRACE("cmdName={}",cmdName);
571 QCString saveCmdName = cmdName;
572 Token tok=tokenizer.lex();
573 if (!tok.is(TokenRetval::TK_WHITESPACE))
574 {
575 warn_doc_error(context.fileName,tokenizer.getLineNr(),"expected whitespace after \\{} command",
576 saveCmdName);
577 return tok;
578 }
579 tok = tokenizer.lex();
580 while (!tok.is_any_of(TokenRetval::TK_NONE, TokenRetval::TK_EOF, TokenRetval::TK_WHITESPACE,
581 TokenRetval::TK_NEWPARA, TokenRetval::TK_LISTITEM, TokenRetval::TK_ENDLIST)
582 )
583 {
584 static const reg::Ex specialChar(R"([.,|()\[\]:;?])");
585 if (tok.is(TokenRetval::TK_WORD) && context.token->name.length()==1 &&
586 reg::match(context.token->name.str(),specialChar))
587 {
588 // special character that ends the markup command
589 return tok;
590 }
591 if (!defaultHandleToken(parent,tok,children))
592 {
593 switch (tok.value())
594 {
595 case TokenRetval::TK_HTMLTAG:
596 if (insideLI(parent) && Mappers::htmlTagMapper->map(context.token->name)!=HtmlTagType::UNKNOWN && context.token->endTag)
597 {
598 // ignore </li> as the end of a style command
599 }
600 else
601 {
602 AUTO_TRACE_EXIT("end tok={}",tok.to_string());
603 return tok;
604 }
605 break;
606 default:
607 errorHandleDefaultToken(parent,tok,children,"\\" + saveCmdName + " command");
608 break;
609 }
610 break;
611 }
612 tok = tokenizer.lex();
613 }
614 AUTO_TRACE_EXIT("end tok={}",tok.to_string());
615 return (tok.is_any_of(TokenRetval::TK_NEWPARA,TokenRetval::TK_LISTITEM,TokenRetval::TK_ENDLIST)) ? tok : Token::make_RetVal_OK();
616}
617
618/*! Called when a style change starts. For instance a <b> command is
619 * encountered.
620 */
622 DocStyleChange::Style s,const QCString &tagName,const HtmlAttribList *attribs)
623{
624 AUTO_TRACE("tagName={}",tagName);
625 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),s,tagName,TRUE,
626 context.fileName,tokenizer.getLineNr(),attribs);
627 context.styleStack.push(&children.back());
628}
629
630/*! Called when a style change ends. For instance a </b> command is
631 * encountered.
632 */
634 DocStyleChange::Style s,const QCString &tagName)
635{
636 AUTO_TRACE("tagName={}",tagName);
637 QCString tagNameLower = QCString(tagName).lower();
638
639 auto topStyleChange = [](const DocStyleChangeStack &stack) -> const DocStyleChange &
640 {
641 return std::get<DocStyleChange>(*stack.top());
642 };
643
644 if (context.styleStack.empty() || // no style change
645 topStyleChange(context.styleStack).style()!=s || // wrong style change
646 topStyleChange(context.styleStack).tagName()!=tagNameLower || // wrong style change
647 topStyleChange(context.styleStack).position()!=context.nodeStack.size() // wrong position
648 )
649 {
650 if (context.styleStack.empty())
651 {
652 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </{0}> tag without matching <{0}>",tagName);
653 }
654 else if (topStyleChange(context.styleStack).tagName()!=tagNameLower ||
655 topStyleChange(context.styleStack).style()!=s)
656 {
657 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </{}> tag while expecting </{}>",
658 tagName,topStyleChange(context.styleStack).tagName());
659 }
660 else
661 {
662 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found </{}> at different nesting level ({}) than expected ({})",
663 tagName,context.nodeStack.size(),topStyleChange(context.styleStack).position());
664 }
665 }
666 else // end the section
667 {
668 children.append<DocStyleChange>(
669 this,parent,context.nodeStack.size(),s,
670 topStyleChange(context.styleStack).tagName(),FALSE);
671 context.styleStack.pop();
672 }
673}
674
675/*! Called at the end of a paragraph to close all open style changes
676 * (e.g. a <b> without a </b>). The closed styles are pushed onto a stack
677 * and entered again at the start of a new paragraph.
678 */
680{
681 AUTO_TRACE();
682 if (!context.styleStack.empty())
683 {
684 const DocStyleChange *sc = &std::get<DocStyleChange>(*context.styleStack.top());
685 while (sc && sc->position()>=context.nodeStack.size())
686 { // there are unclosed style modifiers in the paragraph
687 children.append<DocStyleChange>(this,parent,context.nodeStack.size(),
688 sc->style(),sc->tagName(),FALSE);
689 context.initialStyleStack.push(context.styleStack.top());
690 context.styleStack.pop();
691 sc = !context.styleStack.empty() ? &std::get<DocStyleChange>(*context.styleStack.top()) : nullptr;
692 }
693 }
694}
695
697{
698 AUTO_TRACE();
699 while (!context.initialStyleStack.empty())
700 {
701 const DocStyleChange &sc = std::get<DocStyleChange>(*context.initialStyleStack.top());
702 handleStyleEnter(parent,children,sc.style(),sc.tagName(),&sc.attribs());
703 context.initialStyleStack.pop();
704 }
705}
706
708 const HtmlAttribList &tagHtmlAttribs)
709{
710 AUTO_TRACE();
711 size_t index=0;
712 Token retval = Token::make_RetVal_OK();
713 for (const auto &opt : tagHtmlAttribs)
714 {
715 if (opt.name=="name" || opt.name=="id") // <a name=label> or <a id=label> tag
716 {
717 if (!opt.value.isEmpty())
718 {
719 children.append<DocAnchor>(this,parent,opt.value,TRUE);
720 break; // stop looking for other tag attribs
721 }
722 else
723 {
724 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <a> tag with name option but without value!");
725 }
726 }
727 else if (opt.name=="href") // <a href=url>..</a> tag
728 {
729 // copy attributes
730 HtmlAttribList attrList = tagHtmlAttribs;
731 // and remove the href attribute
732 attrList.erase(attrList.begin()+index);
733 QCString relPath;
734 if (opt.value.at(0) != '#') relPath = context.relPath;
735 children.append<DocHRef>(this, parent, attrList,
736 opt.value, relPath,
737 convertNameToFile(context.fileName, FALSE, TRUE));
738 context.insideHtmlLink=TRUE;
739 retval = children.get_last<DocHRef>()->parse();
740 context.insideHtmlLink=FALSE;
741 tokenizer.setStatePara();
742 break;
743 }
744 else // unsupported option for tag a
745 {
746 }
747 ++index;
748 }
749 return retval;
750}
751
753{
754 AUTO_TRACE();
755 if (!context.initialStyleStack.empty())
756 {
757 QCString tagName = std::get<DocStyleChange>(*context.initialStyleStack.top()).tagName();
758 QCString fileName = std::get<DocStyleChange>(*context.initialStyleStack.top()).fileName();
759 int lineNr = std::get<DocStyleChange>(*context.initialStyleStack.top()).lineNr();
760 context.initialStyleStack.pop();
762 if (lineNr != -1)
763 {
764 warn_doc_error(context.fileName,tokenizer.getLineNr(),
765 "end of comment block while expecting "
766 "command </{}> (Probable start '{}' at line {})",tagName, fileName, lineNr);
767 }
768 else
769 {
770 warn_doc_error(context.fileName,tokenizer.getLineNr(),
771 "end of comment block while expecting command </{}>",tagName);
772 }
773 }
774}
775
776void DocParser::handleLinkedWord(DocNodeVariant *parent,DocNodeList &children,bool ignoreAutoLinkFlag)
777{
778 // helper to check if word w starts with any of the words in AUTOLINK_IGNORE_WORDS
779 auto ignoreWord = [](const QCString &w) -> bool {
780 const auto &list = Config_getList(AUTOLINK_IGNORE_WORDS);
781 return std::find_if(list.begin(), list.end(),
782 [&w](const auto &ignore) { return w.startsWith(ignore); }
783 )!=list.end();
784 };
785 QCString name = linkToText(context.lang,context.token->name,TRUE);
786 AUTO_TRACE("word={}",name);
787 bool autolinkSupport = Config_getBool(AUTOLINK_SUPPORT);
788 if ((!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 switch (Mappers::htmlTagMapper->map(tokenName))
1450 {
1452 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <div> tag in heading");
1453 break;
1455 warn_doc_error(context.fileName,tokenizer.getLineNr(),"found <pre> tag in heading");
1456 break;
1458 if (!context.token->endTag)
1459 {
1460 handleStyleEnter(parent,children,DocStyleChange::Bold,tokenName,&context.token->attribs);
1461 }
1462 else
1463 {
1464 handleStyleLeave(parent,children,DocStyleChange::Bold,tokenName);
1465 }
1466 break;
1468 if (!context.token->endTag)
1469 {
1470 handleStyleEnter(parent,children,DocStyleChange::S,tokenName,&context.token->attribs);
1471 }
1472 else
1473 {
1474 handleStyleLeave(parent,children,DocStyleChange::S,tokenName);
1475 }
1476 break;
1478 if (!context.token->endTag)
1479 {
1480 handleStyleEnter(parent,children,DocStyleChange::Strike,tokenName,&context.token->attribs);
1481 }
1482 else
1483 {
1485 }
1486 break;
1488 if (!context.token->endTag)
1489 {
1490 handleStyleEnter(parent,children,DocStyleChange::Del,tokenName,&context.token->attribs);
1491 }
1492 else
1493 {
1494 handleStyleLeave(parent,children,DocStyleChange::Del,tokenName);
1495 }
1496 break;
1498 if (!context.token->endTag)
1499 {
1500 handleStyleEnter(parent,children,DocStyleChange::Underline,tokenName,&context.token->attribs);
1501 }
1502 else
1503 {
1505 }
1506 break;
1508 if (!context.token->endTag)
1509 {
1510 handleStyleEnter(parent,children,DocStyleChange::Ins,tokenName,&context.token->attribs);
1511 }
1512 else
1513 {
1514 handleStyleLeave(parent,children,DocStyleChange::Ins,tokenName);
1515 }
1516 break;
1518 case HtmlTagType::XML_C:
1519 if (!context.token->endTag)
1520 {
1521 handleStyleEnter(parent,children,DocStyleChange::Code,tokenName,&context.token->attribs);
1522 }
1523 else
1524 {
1525 handleStyleLeave(parent,children,DocStyleChange::Code,tokenName);
1526 }
1527 break;
1529 if (!context.token->endTag)
1530 {
1531 handleStyleEnter(parent,children,DocStyleChange::Kbd,tokenName,&context.token->attribs);
1532 }
1533 else
1534 {
1535 handleStyleLeave(parent,children,DocStyleChange::Kbd,tokenName);
1536 }
1537 break;
1539 if (!context.token->endTag)
1540 {
1541 handleStyleEnter(parent,children,DocStyleChange::Italic,tokenName,&context.token->attribs);
1542 }
1543 else
1544 {
1546 }
1547 break;
1549 if (!context.token->endTag)
1550 {
1551 handleStyleEnter(parent,children,DocStyleChange::Subscript,tokenName,&context.token->attribs);
1552 }
1553 else
1554 {
1556 }
1557 break;
1559 if (!context.token->endTag)
1560 {
1561 handleStyleEnter(parent,children,DocStyleChange::Superscript,tokenName,&context.token->attribs);
1562 }
1563 else
1564 {
1566 }
1567 break;
1569 if (!context.token->endTag)
1570 {
1571 handleStyleEnter(parent,children,DocStyleChange::Center,tokenName,&context.token->attribs);
1572 }
1573 else
1574 {
1576 }
1577 break;
1579 if (!context.token->endTag)
1580 {
1581 handleStyleEnter(parent,children,DocStyleChange::Small,tokenName,&context.token->attribs);
1582 }
1583 else
1584 {
1585 handleStyleLeave(parent,children,DocStyleChange::Small,tokenName);
1586 }
1587 break;
1589 if (!context.token->endTag)
1590 {
1591 handleStyleEnter(parent,children,DocStyleChange::Cite,tokenName,&context.token->attribs);
1592 }
1593 else
1594 {
1595 handleStyleLeave(parent,children,DocStyleChange::Cite,tokenName);
1596 }
1597 break;
1599 if (!context.token->endTag)
1600 handleImg(parent,children,context.token->attribs);
1601 break;
1602 default:
1603 return FALSE;
1604 break;
1605 }
1606 }
1607 break;
1608 case TokenRetval::TK_SYMBOL:
1609 {
1612 {
1613 children.append<DocSymbol>(this,parent,s);
1614 }
1615 else
1616 {
1617 return FALSE;
1618 }
1619 }
1620 break;
1621 case TokenRetval::TK_WHITESPACE:
1622 case TokenRetval::TK_NEWPARA:
1623handlepara:
1624 if (insidePRE(parent) || !children.empty())
1625 {
1626 children.append<DocWhiteSpace>(this,parent,context.token->chars);
1627 }
1628 break;
1629 case TokenRetval::TK_LNKWORD:
1630 if (handleWord)
1631 {
1632 handleLinkedWord(parent,children);
1633 }
1634 else
1635 return FALSE;
1636 break;
1637 case TokenRetval::TK_WORD:
1638 if (handleWord)
1639 {
1640 children.append<DocWord>(this,parent,context.token->name);
1641 }
1642 else
1643 return FALSE;
1644 break;
1645 case TokenRetval::TK_URL:
1646 if (context.insideHtmlLink)
1647 {
1648 children.append<DocWord>(this,parent,context.token->name);
1649 }
1650 else
1651 {
1652 children.append<DocURL>(this,parent,context.token->name,context.token->isEMailAddr);
1653 }
1654 break;
1655 default:
1656 return FALSE;
1657 }
1658 return TRUE;
1659}
1660
1661//---------------------------------------------------------------------------
1662
1664{
1665 AUTO_TRACE();
1666 bool found=FALSE;
1667 size_t index=0;
1668 for (const auto &opt : tagHtmlAttribs)
1669 {
1670 AUTO_TRACE_ADD("option name={} value='{}'",opt.name,opt.value);
1671 if (opt.name=="src" && !opt.value.isEmpty())
1672 {
1673 // copy attributes
1674 HtmlAttribList attrList = tagHtmlAttribs;
1675 // and remove the src attribute
1676 attrList.erase(attrList.begin()+index);
1678 children.append<DocImage>(
1679 this,parent,attrList,
1680 findAndCopyImage(opt.value,t,false),
1681 t,opt.value);
1682 found = TRUE;
1683 }
1684 ++index;
1685 }
1686 if (!found)
1687 {
1688 warn_doc_error(context.fileName,tokenizer.getLineNr(),"IMG tag does not have a SRC attribute!");
1689 }
1690}
1691
1692//---------------------------------------------------------------------------
1693
1695 const QCString &doc)
1696{
1697 AUTO_TRACE();
1698 Token retval = Token::make_RetVal_OK();
1699
1700 if (doc.isEmpty()) return retval;
1701
1702 tokenizer.init(doc.data(),context.fileName,context.markdownSupport,context.insideHtmlLink);
1703
1704 // first parse any number of paragraphs
1705 bool isFirst=TRUE;
1706 DocPara *lastPar=!children.empty() ? std::get_if<DocPara>(&children.back()): nullptr;
1707 if (lastPar)
1708 { // last child item was a paragraph
1709 isFirst=FALSE;
1710 }
1711 do
1712 {
1713 children.append<DocPara>(this,parent);
1714 DocPara *par = children.get_last<DocPara>();
1715 if (isFirst) { par->markFirst(); isFirst=FALSE; }
1716 retval=par->parse();
1717 if (!par->isEmpty())
1718 {
1719 if (lastPar) lastPar->markLast(FALSE);
1720 lastPar=par;
1721 }
1722 else
1723 {
1724 children.pop_back();
1725 }
1726 } while (retval.is(TokenRetval::TK_NEWPARA));
1727 if (lastPar) lastPar->markLast();
1728
1729 AUTO_TRACE_EXIT("isFirst={} isLast={}",lastPar?lastPar->isFirst():-1,lastPar?lastPar->isLast():-1);
1730 return retval;
1731}
1732
1733//---------------------------------------------------------------------------
1734
1736{
1737 AUTO_TRACE("file={} text={}",file,text);
1738 bool ambig = false;
1739 QCString filePath = findFilePath(file,ambig);
1740 if (!filePath.isEmpty())
1741 {
1742 text = fileToString(filePath,Config_getBool(FILTER_SOURCE_FILES));
1743 if (ambig)
1744 {
1745 warn_doc_error(context.fileName,tokenizer.getLineNr(),"included file name '{}' is ambiguous"
1746 "Possible candidates:\n{}",file, showFileDefMatches(Doxygen::exampleNameLinkedMap,file));
1747 }
1748 }
1749 else
1750 {
1751 warn_doc_error(context.fileName,tokenizer.getLineNr(),"included file '{}' is not found. "
1752 "Check your EXAMPLE_PATH",file);
1753 }
1754}
1755
1756//---------------------------------------------------------------------------
1757
1758static QCString extractCopyDocId(const char *data, size_t &j, size_t len)
1759{
1760 size_t s=j;
1761 int round=0;
1762 bool insideDQuote=FALSE;
1763 bool insideSQuote=FALSE;
1764 bool found=FALSE;
1765 while (j<len && !found)
1766 {
1767 if (!insideSQuote && !insideDQuote)
1768 {
1769 switch (data[j])
1770 {
1771 case '(': round++; break;
1772 case ')': round--; break;
1773 case '"': insideDQuote=TRUE; break;
1774 case '\'': insideSQuote=TRUE; break;
1775 case '\\': // fall through, begin of command
1776 case '@': // fall through, begin of command
1777 case '\t': // fall through
1778 case '\n':
1779 found=(round==0);
1780 break;
1781 case ' ': // allow spaces in cast operator (see issue #11169)
1782 found=(round==0) && (j<8 || qstrncmp(data+j-8,"operator",8)!=0);
1783 break;
1784 }
1785 }
1786 else if (insideSQuote) // look for single quote end
1787 {
1788 if (data[j]=='\'' && (j==0 || data[j]!='\\'))
1789 {
1790 insideSQuote=FALSE;
1791 }
1792 }
1793 else if (insideDQuote) // look for double quote end
1794 {
1795 if (data[j]=='"' && (j==0 || data[j]!='\\'))
1796 {
1797 insideDQuote=FALSE;
1798 }
1799 }
1800 if (!found) j++;
1801 }
1802
1803 // include const and volatile
1804 if (qstrncmp(data+j," const",6)==0)
1805 {
1806 j+=6;
1807 }
1808 else if (qstrncmp(data+j," volatile",9)==0)
1809 {
1810 j+=9;
1811 }
1812
1813 // allow '&' or '&&' or ' &' or ' &&' at the end
1814 size_t k=j;
1815 while (k<len && data[k]==' ') k++;
1816 if (k<len-1 && data[k]=='&' && data[k+1]=='&') j=k+2;
1817 else if (k<len && data[k]=='&' ) j=k+1;
1818
1819 // do not include punctuation added by Definition::_setBriefDescription()
1820 size_t e=j;
1821 if (j>0 && data[j-1]=='.') { e--; }
1822 QCString id(data+s,e-s);
1823 //printf("extractCopyDocId='%s' input='%s'\n",qPrint(id),&data[s]);
1824 return id;
1825}
1826
1827// macro to check if the input starts with a specific command.
1828// note that data[i] should point to the start of the command (\ or @ character)
1829// and the sizeof(str) returns the size of str including the '\0' terminator;
1830// a fact we abuse to skip over the start of the command character.
1831#define CHECK_FOR_COMMAND(str,action) \
1832 do if ((i+sizeof(str)<len) && qstrncmp(data+i+1,str,sizeof(str)-1)==0) \
1833 { j=i+sizeof(str); action; } while(0)
1834
1835static size_t isCopyBriefOrDetailsCmd(const char *data, size_t i,size_t len,bool &brief)
1836{
1837 size_t j=0;
1838 if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1839 {
1840 CHECK_FOR_COMMAND("copybrief",brief=TRUE); // @copybrief or \copybrief
1841 CHECK_FOR_COMMAND("copydetails",brief=FALSE); // @copydetails or \copydetails
1842 }
1843 return j;
1844}
1845
1846static size_t isVerbatimSection(const char *data,size_t i,size_t len,QCString &endMarker)
1847{
1848 size_t j=0;
1849 if (i==0 || (data[i-1]!='@' && data[i-1]!='\\')) // not an escaped command
1850 {
1851 CHECK_FOR_COMMAND("dot",endMarker="enddot");
1852 CHECK_FOR_COMMAND("icode",endMarker="endicode");
1853 CHECK_FOR_COMMAND("code",endMarker="endcode");
1854 CHECK_FOR_COMMAND("msc",endMarker="endmsc");
1855 CHECK_FOR_COMMAND("iverbatim",endMarker="endiverbatim");
1856 CHECK_FOR_COMMAND("verbatim",endMarker="endverbatim");
1857 CHECK_FOR_COMMAND("iliteral",endMarker="endiliteral");
1858 CHECK_FOR_COMMAND("latexonly",endMarker="endlatexonly");
1859 CHECK_FOR_COMMAND("htmlonly",endMarker="endhtmlonly");
1860 CHECK_FOR_COMMAND("xmlonly",endMarker="endxmlonly");
1861 CHECK_FOR_COMMAND("rtfonly",endMarker="endrtfonly");
1862 CHECK_FOR_COMMAND("manonly",endMarker="endmanonly");
1863 CHECK_FOR_COMMAND("docbookonly",endMarker="enddocbookonly");
1864 CHECK_FOR_COMMAND("startuml",endMarker="enduml");
1865 }
1866 //printf("isVerbatimSection(%s)=%d)\n",qPrint(QCString(&data[i]).left(10)),j);
1867 return j;
1868}
1869
1870static size_t skipToEndMarker(const char *data,size_t i,size_t len,const QCString &endMarker)
1871{
1872 while (i<len)
1873 {
1874 if ((data[i]=='@' || data[i]=='\\') && // start of command character
1875 (i==0 || (data[i-1]!='@' && data[i-1]!='\\'))) // that is not escaped
1876 {
1877 if (i+endMarker.length()+1<=len && qstrncmp(data+i+1,endMarker.data(),endMarker.length())==0)
1878 {
1879 return i+endMarker.length()+1;
1880 }
1881 }
1882 i++;
1883 }
1884 // oops no endmarker found...
1885 return i<len ? i+1 : len;
1886}
1887
1888
1889QCString DocParser::processCopyDoc(const char *data,size_t &len)
1890{
1891 AUTO_TRACE("data={} len={}",Trace::trunc(data),len);
1892 GrowBuf buf;
1893 size_t i=0;
1894 int lineNr = tokenizer.getLineNr();
1895 while (i<len)
1896 {
1897 char c = data[i];
1898 if (c=='@' || c=='\\') // look for a command
1899 {
1900 bool isBrief=TRUE;
1901 size_t j=isCopyBriefOrDetailsCmd(data,i,len,isBrief);
1902 if (j>0)
1903 {
1904 // skip whitespace
1905 while (j<len && (data[j]==' ' || data[j]=='\t')) j++;
1906 // extract the argument
1907 QCString id = extractCopyDocId(data,j,len);
1908 const Definition *def = nullptr;
1909 QCString doc,brief;
1910 //printf("resolving docs='%s'\n",qPrint(id));
1911 bool found = findDocsForMemberOrCompound(id,&doc,&brief,&def);
1912 if (found && def->isReference())
1913 {
1914 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1915 "@copy{} or @copydoc target '{}' found but is from a tag file, skipped",
1916 isBrief?"brief":"details", id);
1917 }
1918 else if (found)
1919 {
1920 //printf("found it def=%p brief='%s' doc='%s' isBrief=%d\n",def,qPrint(brief),qPrint(doc),isBrief);
1921 auto it = std::find(context.copyStack.begin(),context.copyStack.end(),def);
1922 if (it==context.copyStack.end()) // definition not parsed earlier
1923 {
1924 QCString orgFileName = context.fileName;
1925 context.copyStack.push_back(def);
1926 auto addDocs = [&](const QCString &file_,int line_,const QCString &doc_)
1927 {
1928 buf.addStr(" \\ifile \""+file_+"\" ");
1929 buf.addStr("\\iline "+QCString().setNum(line_)+" \\ilinebr ");
1930 size_t len_ = doc_.length();
1931 buf.addStr(processCopyDoc(doc_.data(),len_));
1932 };
1933 if (isBrief)
1934 {
1935 addDocs(def->briefFile(),def->briefLine(),brief);
1936 }
1937 else
1938 {
1939 addDocs(def->docFile(),def->docLine(),doc);
1941 {
1942 const MemberDef *md = toMemberDef(def);
1943 const ArgumentList &docArgList = md->templateMaster() ?
1944 md->templateMaster()->argumentList() :
1945 md->argumentList();
1946 buf.addStr(inlineArgListToDoc(docArgList));
1947 }
1948 }
1949 context.copyStack.pop_back();
1950 buf.addStr(" \\ilinebr \\ifile \""+context.fileName+"\" ");
1951 buf.addStr("\\iline "+QCString().setNum(lineNr)+" ");
1952 }
1953 else
1954 {
1955 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1956 "Found recursive @copy{} or @copydoc relation for argument '{}'.",
1957 isBrief?"brief":"details",id);
1958 }
1959 }
1960 else
1961 {
1962 warn_doc_error(context.fileName,tokenizer.getLineNr(),
1963 "@copy{} or @copydoc target '{}' not found", isBrief?"brief":"details",id);
1964 }
1965 // skip over command
1966 i=j;
1967 }
1968 else
1969 {
1970 QCString endMarker;
1971 size_t k = isVerbatimSection(data,i,len,endMarker);
1972 if (k>0)
1973 {
1974 size_t orgPos = i;
1975 i=skipToEndMarker(data,k,len,endMarker);
1976 buf.addStr(data+orgPos,i-orgPos);
1977 // TODO: adjust lineNr
1978 }
1979 else
1980 {
1981 buf.addChar(c);
1982 i++;
1983 }
1984 }
1985 }
1986 else // not a command, just copy
1987 {
1988 buf.addChar(c);
1989 i++;
1990 lineNr += (c=='\n') ? 1 : 0;
1991 }
1992 }
1993 len = buf.getPos();
1994 buf.addChar(0);
1995 AUTO_TRACE_EXIT("result={}",Trace::trunc(buf.get()));
1996 return buf.get();
1997}
1998
1999
2000//---------------------------------------------------------------------------
2001
2003 const QCString &fileName,int startLine,
2004 const Definition *ctx,const MemberDef *md,
2005 const QCString &input,bool indexWords,
2006 bool isExample, const QCString &exampleName,
2007 bool singleLine, bool linkFromIndex,
2008 bool markdownSupport)
2009{
2010 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2011 assert(parser!=nullptr);
2012 if (parser==nullptr) return nullptr;
2013 //printf("validatingParseDoc(%s,%s)=[%s]\n",ctx?qPrint(ctx->name()):"<none>",
2014 // md?qPrint(md->name()):"<none>",
2015 // input);
2016 //printf("========== validating %s at line %d\n",qPrint(fileName),startLine);
2017 //printf("---------------- input --------------------\n%s\n----------- end input -------------------\n",qPrint(input));
2018
2019 // set initial token
2020 parser->context.token = parser->tokenizer.resetToken();
2021
2022 if (ctx && ctx!=Doxygen::globalScope &&
2025 )
2026 )
2027 {
2029 }
2030 else if (ctx && ctx->definitionType()==Definition::TypePage)
2031 {
2032 const Definition *scope = (toPageDef(ctx))->getPageScope();
2033 if (scope && scope!=Doxygen::globalScope)
2034 {
2035 parser->context.context = substitute(scope->name(),getLanguageSpecificSeparator(scope->getLanguage(),true),"::");
2036 }
2037 }
2038 else if (ctx && ctx->definitionType()==Definition::TypeGroup)
2039 {
2040 const Definition *scope = (toGroupDef(ctx))->getGroupScope();
2041 if (scope && scope!=Doxygen::globalScope)
2042 {
2043 parser->context.context = substitute(scope->name(),getLanguageSpecificSeparator(scope->getLanguage(),true),"::");
2044 }
2045 }
2046 else
2047 {
2048 parser->context.context = "";
2049 }
2050 parser->context.scope = ctx;
2051 parser->context.lang = getLanguageFromFileName(fileName);
2052
2053 if (indexWords && Doxygen::searchIndex.enabled())
2054 {
2055 if (md)
2056 {
2057 parser->context.searchUrl=md->getOutputFileBase();
2058 Doxygen::searchIndex.setCurrentDoc(md,md->anchor(),false);
2059 }
2060 else if (ctx)
2061 {
2062 parser->context.searchUrl=ctx->getOutputFileBase();
2063 Doxygen::searchIndex.setCurrentDoc(ctx,ctx->anchor(),false);
2064 }
2065 }
2066 else
2067 {
2068 parser->context.searchUrl="";
2069 }
2070
2071 parser->context.fileName = fileName;
2072 parser->context.relPath = (!linkFromIndex && ctx) ?
2074 QCString("");
2075 //printf("ctx->name=%s relPath=%s\n",qPrint(ctx->name()),qPrint(parser->context.relPath));
2076 parser->context.memberDef = md;
2077 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
2078 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
2079 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
2080 parser->context.inSeeBlock = FALSE;
2081 parser->context.xmlComment = FALSE;
2082 parser->context.insideHtmlLink = FALSE;
2083 parser->context.includeFileText = "";
2084 parser->context.includeFileOffset = 0;
2085 parser->context.includeFileLength = 0;
2086 parser->context.isExample = isExample;
2087 parser->context.exampleName = exampleName;
2088 parser->context.hasParamCommand = FALSE;
2089 parser->context.hasReturnCommand = FALSE;
2090 parser->context.retvalsFound.clear();
2091 parser->context.paramsFound.clear();
2092 parser->context.markdownSupport = markdownSupport;
2093
2094 //printf("Starting comment block at %s:%d\n",qPrint(parser->context.fileName),startLine);
2095 parser->tokenizer.setLineNr(startLine);
2096 size_t ioLen = input.length();
2097 QCString inpStr = parser->processCopyDoc(input.data(),ioLen);
2098 if (inpStr.isEmpty() || inpStr.at(inpStr.length()-1)!='\n')
2099 {
2100 inpStr+='\n';
2101 }
2102 //printf("processCopyDoc(in='%s' out='%s')\n",input,qPrint(inpStr));
2103 parser->tokenizer.init(inpStr.data(),parser->context.fileName,
2105
2106 // build abstract syntax tree
2107 auto ast = std::make_unique<DocNodeAST>(DocRoot(parser,md!=nullptr,singleLine));
2108 std::get<DocRoot>(ast->root).parse();
2109
2111 {
2112 // pretty print the result
2113 std::visit(PrintDocVisitor{},ast->root);
2114 }
2115
2116 if (md && md->isFunction())
2117 {
2119 }
2121
2122 // reset token
2123 parser->tokenizer.resetToken();
2124
2125 //printf(">>>>>> end validatingParseDoc(%s,%s)\n",ctx?qPrint(ctx->name()):"<none>",
2126 // md?qPrint(md->name()):"<none>");
2127
2128 return ast;
2129}
2130
2132{
2133 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2134 assert(parser!=nullptr);
2135 if (parser==nullptr) return nullptr;
2136
2137 // set initial token
2138 parser->context.token = parser->tokenizer.resetToken();
2139
2140 //printf("------------ input ---------\n%s\n"
2141 // "------------ end input -----\n",input);
2142 //parser->context.token = new TokenInfo;
2143 parser->context.context = "";
2144 parser->context.fileName = "<parseText>";
2145 parser->context.relPath = "";
2146 parser->context.memberDef = nullptr;
2147 while (!parser->context.nodeStack.empty()) parser->context.nodeStack.pop();
2148 while (!parser->context.styleStack.empty()) parser->context.styleStack.pop();
2149 while (!parser->context.initialStyleStack.empty()) parser->context.initialStyleStack.pop();
2150 parser->context.inSeeBlock = FALSE;
2151 parser->context.xmlComment = FALSE;
2152 parser->context.insideHtmlLink = FALSE;
2153 parser->context.includeFileText = "";
2154 parser->context.includeFileOffset = 0;
2155 parser->context.includeFileLength = 0;
2156 parser->context.isExample = FALSE;
2157 parser->context.exampleName = "";
2158 parser->context.hasParamCommand = FALSE;
2159 parser->context.hasReturnCommand = FALSE;
2160 parser->context.retvalsFound.clear();
2161 parser->context.paramsFound.clear();
2162 parser->context.searchUrl="";
2164 parser->context.markdownSupport = Config_getBool(MARKDOWN_SUPPORT);
2165
2166
2167 auto ast = std::make_unique<DocNodeAST>(DocText(parser));
2168
2169 if (!input.isEmpty())
2170 {
2171 parser->tokenizer.setLineNr(1);
2172 parser->tokenizer.init(input.data(),parser->context.fileName,
2174
2175 // build abstract syntax tree
2176 std::get<DocText>(ast->root).parse();
2177
2179 {
2180 // pretty print the result
2181 std::visit(PrintDocVisitor{},ast->root);
2182 }
2183 }
2184
2185 return ast;
2186}
2187
2188IDocNodeASTPtr createRef(IDocParser &parserIntf,const QCString &target,const QCString &context, const QCString &srcFile, int srcLine )
2189{
2190 DocParser *parser = dynamic_cast<DocParser*>(&parserIntf);
2191 assert(parser!=nullptr);
2192 if (parser==nullptr) return nullptr;
2193 if (!srcFile.isEmpty())
2194 {
2195 parser->context.fileName = srcFile;
2196 parser->tokenizer.setLineNr(srcLine);
2197 }
2198 return std::make_unique<DocNodeAST>(DocRef(parser,nullptr,target,context));
2199}
2200
2201void docFindSections(const QCString &input,
2202 const Definition *d,
2203 const QCString &fileName)
2204{
2205 DocParser parser;
2206 parser.tokenizer.findSections(input,d,fileName);
2207}
2208
This class represents an function or template argument list.
Definition arguments.h:60
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
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:228
Node representing an item of a cross-referenced list.
Definition docnode.h:524
Node representing a Hypertext reference.
Definition docnode.h:817
Node representing an image.
Definition docnode.h:636
@ DocBook
Definition docnode.h:638
Node representing an internal reference to some item.
Definition docnode.h:801
Node representing a word that can be linked to something.
Definition docnode.h:164
Node representing a paragraph in the documentation tree.
Definition docnode.h:1074
bool isLast() const
Definition docnode.h:1082
void markLast(bool v=TRUE)
Definition docnode.h:1080
bool isFirst() const
Definition docnode.h:1081
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:772
Root node of documentation tree.
Definition docnode.h:1307
Node representing a separator.
Definition docnode.h:360
Node representing a style change.
Definition docnode.h:264
const HtmlAttribList & attribs() const
Definition docnode.h:306
QCString tagName() const
Definition docnode.h:307
Style style() const
Definition docnode.h:302
size_t position() const
Definition docnode.h:305
Node representing a special symbol.
Definition docnode.h:323
static HtmlEntityMapper::SymType decodeSymbol(const QCString &symName)
Definition docnode.cpp:152
Root node of a text fragment.
Definition docnode.h:1298
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:187
Node representing a verbatim, unparsed text fragment.
Definition docnode.h:371
Node representing some amount of white space.
Definition docnode.h:349
Node representing a word.
Definition docnode.h:152
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:34
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
Iterator class to iterator 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: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:1324
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:1894
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: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: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
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:1393
T * get_last()
Returns a pointer to the last element in the list if that element exists and holds a T,...
Definition docnode.h:1404
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:3431
QCString linkToText(SrcLangExt lang, const QCString &link, bool isFileName)
Definition util.cpp:3159
SrcLangExt getLanguageFromFileName(const QCString &fileName, SrcLangExt defLang)
Definition util.cpp:5645
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:2904
QCString relativePathToRoot(const QCString &name)
Definition util.cpp:4019
QCString showFileDefMatches(const FileNameLinkedMap *fnMap, const QCString &n)
Definition util.cpp:3474
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:3944
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:6326
GetDefResult getDefs(const GetDefInput &input)
Definition util.cpp:2762
StringVector split(const std::string &s, const std::string &delimiter)
split input string s by string delimiter delimiter.
Definition util.cpp:7042
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:6266
QCString inlineArgListToDoc(const ArgumentList &al)
Definition util.cpp:1156
FileDef * findFileDef(const FileNameLinkedMap *fnMap, const QCString &n, bool &ambig)
Definition util.cpp:3348
A bunch of utility functions.