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