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