1 /*****************************************************************************
2  *
3  *
4  * Copyright (C) 1997-2015 by Dimitri van Heesch.
5  *
6  * Permission to use, copy, modify, and distribute this software and its
7  * documentation under the terms of the GNU General Public License is hereby
8  * granted. No representations are made about the suitability of this software
9  * for any purpose. It is provided "as is" without express or implied warranty.
10  * See the GNU General Public License for more details.
11  *
12  * Documents produced by Doxygen are derivative works derived from the
13  * input used in their production; they are not affected by this license.
14  *
15  */
16 
17 #include <stdlib.h>
18 #include <errno.h>
19 #include <math.h>
20 #include <limits.h>
21 #include <string.h>
22 #include <assert.h>
23 
24 #include <mutex>
25 #include <unordered_set>
26 #include <codecvt>
27 #include <algorithm>
28 #include <ctime>
29 #include <cctype>
30 #include <cinttypes>
31 #include <sstream>
32 
33 #include "md5.h"
34 
35 #include "regex.h"
36 #include "util.h"
37 #include "message.h"
38 #include "classdef.h"
39 #include "filedef.h"
40 #include "doxygen.h"
41 #include "outputlist.h"
42 #include "defargs.h"
43 #include "language.h"
44 #include "config.h"
45 #include "htmlhelp.h"
46 #include "example.h"
47 #include "version.h"
48 #include "groupdef.h"
49 #include "reflist.h"
50 #include "pagedef.h"
51 #include "debug.h"
52 #include "searchindex.h"
53 #include "doxygen.h"
54 #include "textdocvisitor.h"
55 #include "latexdocvisitor.h"
56 #include "portable.h"
57 #include "parserintf.h"
58 #include "bufstr.h"
59 #include "image.h"
60 #include "growbuf.h"
61 #include "entry.h"
62 #include "arguments.h"
63 #include "memberlist.h"
64 #include "classlist.h"
65 #include "namespacedef.h"
66 #include "membername.h"
67 #include "filename.h"
68 #include "membergroup.h"
69 #include "dirdef.h"
70 #include "htmlentity.h"
71 #include "symbolresolver.h"
72 #include "fileinfo.h"
73 #include "dir.h"
74 #include "utf8.h"
75 #include "textstream.h"
76 
77 #define ENABLE_TRACINGSUPPORT 0
78 
79 #if defined(_OS_MAC_) && ENABLE_TRACINGSUPPORT
80 #define TRACINGSUPPORT
81 #endif
82 
83 #ifdef TRACINGSUPPORT
84 #include <execinfo.h>
85 #include <unistd.h>
86 #endif
87 
88 
89 //------------------------------------------------------------------------
90 
91 #define REL_PATH_TO_ROOT "../../"
92 
93 static const char *hex = "0123456789ABCDEF";
94 
95 //------------------------------------------------------------------------
96 // TextGeneratorOLImpl implementation
97 //------------------------------------------------------------------------
98 
TextGeneratorOLImpl(OutputDocInterface & od)99 TextGeneratorOLImpl::TextGeneratorOLImpl(OutputDocInterface &od) : m_od(od)
100 {
101 }
102 
writeString(const QCString & s,bool keepSpaces) const103 void TextGeneratorOLImpl::writeString(const QCString &s,bool keepSpaces) const
104 {
105   if (s.isEmpty()) return;
106   //printf("TextGeneratorOlImpl::writeString('%s',%d)\n",s,keepSpaces);
107   if (keepSpaces)
108   {
109     const char *p=s.data();
110     if (p)
111     {
112       char cs[2];
113       char c;
114       cs[1]='\0';
115       while ((c=*p++))
116       {
117         if (c==' ') m_od.writeNonBreakableSpace(1);
118         else cs[0]=c,m_od.docify(cs);
119       }
120     }
121   }
122   else
123   {
124     m_od.docify(s);
125   }
126 }
127 
writeBreak(int indent) const128 void TextGeneratorOLImpl::writeBreak(int indent) const
129 {
130   m_od.lineBreak("typebreak");
131   int i;
132   for (i=0;i<indent;i++)
133   {
134     m_od.writeNonBreakableSpace(3);
135   }
136 }
137 
writeLink(const QCString & extRef,const QCString & file,const QCString & anchor,const QCString & text) const138 void TextGeneratorOLImpl::writeLink(const QCString &extRef,const QCString &file,
139                                     const QCString &anchor,const QCString &text
140                                    ) const
141 {
142   //printf("TextGeneratorOlImpl::writeLink('%s')\n",text);
143   m_od.writeObjectLink(extRef,file,anchor,text);
144 }
145 
146 //------------------------------------------------------------------------
147 //------------------------------------------------------------------------
148 
149 // an inheritance tree of depth of 100000 should be enough for everyone :-)
150 const int maxInheritanceDepth = 100000;
151 
152 /*!
153   Removes all anonymous scopes from string s
154   Possible examples:
155 \verbatim
156    "bla::@10::blep"      => "bla::blep"
157    "bla::@10::@11::blep" => "bla::blep"
158    "@10::blep"           => "blep"
159    " @10::blep"          => "blep"
160    "@9::@10::blep"       => "blep"
161    "bla::@1"             => "bla"
162    "bla::@1::@2"         => "bla"
163    "bla @1"              => "bla"
164 \endverbatim
165  */
removeAnonymousScopes(const QCString & str)166 QCString removeAnonymousScopes(const QCString &str)
167 {
168   std::string result;
169   if (str.isEmpty()) return QCString(result);
170 
171   // helper to check if the found delimiter starts with a colon
172   auto startsWithColon = [](const std::string &del)
173   {
174     for (size_t i=0;i<del.size();i++)
175     {
176       if (del[i]=='@') return false;
177       else if (del[i]==':') return true;
178     }
179     return false;
180   };
181 
182   // helper to check if the found delimiter ends with a colon
183   auto endsWithColon = [](const std::string &del)
184   {
185     for (int i=(int)del.size()-1;i>=0;i--)
186     {
187       if (del[i]=='@') return false;
188       else if (del[i]==':') return true;
189     }
190     return false;
191   };
192 
193   static const reg::Ex re(R"([\s:]*@\d+[\s:]*)");
194   std::string s = str.str();
195   reg::Iterator iter(s,re);
196   reg::Iterator end;
197   size_t p=0;
198   size_t sl=s.length();
199   bool needsSeparator=false;
200   for ( ; iter!=end ; ++iter)
201   {
202     const auto &match = *iter;
203     size_t i = match.position();
204     if (i>p) // add non-matching prefix
205     {
206       if (needsSeparator) result+="::";
207       needsSeparator=false;
208       result+=s.substr(p,i-p);
209     }
210     std::string delim = match.str();
211     needsSeparator = needsSeparator || (startsWithColon(delim) && endsWithColon(delim));
212     p = match.position()+match.length();
213   }
214   if (p<sl) // add trailing remainder
215   {
216     if (needsSeparator) result+="::";
217     result+=s.substr(p);
218   }
219   return result;
220 }
221 
222 // replace anonymous scopes with __anonymous__ or replacement if provided
replaceAnonymousScopes(const QCString & s,const QCString & replacement)223 QCString replaceAnonymousScopes(const QCString &s,const QCString &replacement)
224 {
225   if (s.isEmpty()) return s;
226   static const reg::Ex marker(R"(@\d+)");
227   std::string result = reg::replace(s.str(),marker,
228                                     replacement.isEmpty() ? replacement.data() : "__anonymous__");
229   //printf("replaceAnonymousScopes('%s')='%s'\n",qPrint(s),qPrint(result));
230   return QCString(result);
231 }
232 
233 
234 // strip anonymous left hand side part of the scope
stripAnonymousNamespaceScope(const QCString & s)235 QCString stripAnonymousNamespaceScope(const QCString &s)
236 {
237   int i,p=0,l;
238   QCString newScope;
239   int sl = s.length();
240   while ((i=getScopeFragment(s,p,&l))!=-1)
241   {
242     //printf("Scope fragment %s\n",qPrint(s.mid(i,l)));
243     if (Doxygen::namespaceLinkedMap->find(s.left(i+l))!=0)
244     {
245       if (s.at(i)!='@')
246       {
247         if (!newScope.isEmpty()) newScope+="::";
248         newScope+=s.mid(i,l);
249       }
250     }
251     else if (i<sl)
252     {
253       if (!newScope.isEmpty()) newScope+="::";
254       newScope+=s.right(sl-i);
255       goto done;
256     }
257     p=i+l;
258   }
259 done:
260   //printf("stripAnonymousNamespaceScope('%s')='%s'\n",qPrint(s),qPrint(newScope));
261   return newScope;
262 }
263 
writePageRef(OutputDocInterface & od,const QCString & cn,const QCString & mn)264 void writePageRef(OutputDocInterface &od,const QCString &cn,const QCString &mn)
265 {
266   od.pushGeneratorState();
267 
268   od.disable(OutputGenerator::Html);
269   od.disable(OutputGenerator::Man);
270   od.disable(OutputGenerator::Docbook);
271   if (Config_getBool(PDF_HYPERLINKS)) od.disable(OutputGenerator::Latex);
272   if (Config_getBool(RTF_HYPERLINKS)) od.disable(OutputGenerator::RTF);
273   od.startPageRef();
274   od.docify(theTranslator->trPageAbbreviation());
275   od.endPageRef(cn,mn);
276 
277   od.popGeneratorState();
278 }
279 
280 /*! Generate a place holder for a position in a list. Used for
281  *  translators to be able to specify different elements orders
282  *  depending on whether text flows from left to right or visa versa.
283  */
generateMarker(int id)284 QCString generateMarker(int id)
285 {
286   const int maxMarkerStrLen = 20;
287   char result[maxMarkerStrLen];
288   qsnprintf(result,maxMarkerStrLen,"@%d",id);
289   return result;
290 }
291 
stripFromPath(const QCString & path,const StringVector & l)292 static QCString stripFromPath(const QCString &path,const StringVector &l)
293 {
294   // look at all the strings in the list and strip the longest match
295   QCString potential;
296   unsigned int length = 0;
297   for (const auto &s : l)
298   {
299     QCString prefix = s.c_str();
300     if (prefix.length() > length &&
301         qstricmp(path.left(prefix.length()),prefix)==0) // case insensitive compare
302     {
303       length = prefix.length();
304       potential = path.right(path.length()-prefix.length());
305     }
306   }
307   if (length) return potential;
308   return path;
309 }
310 
311 /*! strip part of \a path if it matches
312  *  one of the paths in the Config_getList(STRIP_FROM_PATH) list
313  */
stripFromPath(const QCString & path)314 QCString stripFromPath(const QCString &path)
315 {
316   return stripFromPath(path,Config_getList(STRIP_FROM_PATH));
317 }
318 
319 /*! strip part of \a path if it matches
320  *  one of the paths in the Config_getList(INCLUDE_PATH) list
321  */
stripFromIncludePath(const QCString & path)322 QCString stripFromIncludePath(const QCString &path)
323 {
324   return stripFromPath(path,Config_getList(STRIP_FROM_INC_PATH));
325 }
326 
327 /*! try to determine if \a name is a source or a header file name by looking
328  * at the extension. A number of variations is allowed in both upper and
329  * lower case) If anyone knows or uses another extension please let me know :-)
330  */
guessSection(const QCString & name)331 int guessSection(const QCString &name)
332 {
333   QCString n=name.lower();
334   if (n.right(2)==".c"    || // source
335       n.right(3)==".cc"   ||
336       n.right(4)==".cxx"  ||
337       n.right(4)==".cpp"  ||
338       n.right(4)==".c++"  ||
339       n.right(5)==".java" ||
340       n.right(2)==".m"    ||
341       n.right(3)==".mm"   ||
342       n.right(3)==".ii"   || // inline
343       n.right(4)==".ixx"  ||
344       n.right(4)==".ipp"  ||
345       n.right(4)==".i++"  ||
346       n.right(4)==".inl"  ||
347       n.right(4)==".xml"  ||
348       n.right(4)==".lex"  ||
349       n.right(4)==".sql"
350      ) return Entry::SOURCE_SEC;
351   if (n.right(2)==".h"    || // header
352       n.right(3)==".hh"   ||
353       n.right(4)==".hxx"  ||
354       n.right(4)==".hpp"  ||
355       n.right(4)==".h++"  ||
356       n.right(4)==".idl"  ||
357       n.right(4)==".ddl"  ||
358       n.right(5)==".pidl" ||
359       n.right(4)==".ice"
360      ) return Entry::HEADER_SEC;
361   return 0;
362 }
363 
resolveTypeDef(const Definition * context,const QCString & qualifiedName,const Definition ** typedefContext)364 QCString resolveTypeDef(const Definition *context,const QCString &qualifiedName,
365                         const Definition **typedefContext)
366 {
367   //printf("<<resolveTypeDef(%s,%s)\n",
368   //          context ? qPrint(context->name()) : "<none>",qPrint(qualifiedName));
369   QCString result;
370   if (qualifiedName.isEmpty())
371   {
372     //printf("  qualified name empty!\n");
373     return result;
374   }
375 
376   const Definition *mContext=context;
377   if (typedefContext) *typedefContext=context;
378 
379   // see if the qualified name has a scope part
380   int scopeIndex = qualifiedName.findRev("::");
381   QCString resName=qualifiedName;
382   if (scopeIndex!=-1) // strip scope part for the name
383   {
384     resName=qualifiedName.right(qualifiedName.length()-scopeIndex-2);
385     if (resName.isEmpty())
386     {
387       // qualifiedName was of form A:: !
388       //printf("  qualified name of form A::!\n");
389       return result;
390     }
391   }
392   const MemberDef *md=0;
393   while (mContext && md==0)
394   {
395     // step 1: get the right scope
396     const Definition *resScope=mContext;
397     if (scopeIndex!=-1)
398     {
399       // split-off scope part
400       QCString resScopeName = qualifiedName.left(scopeIndex);
401       //printf("resScopeName='%s'\n",qPrint(resScopeName));
402 
403       // look-up scope in context
404       int is,ps=0;
405       int l;
406       while ((is=getScopeFragment(resScopeName,ps,&l))!=-1)
407       {
408         QCString qualScopePart = resScopeName.mid(is,l);
409         QCString tmp = resolveTypeDef(mContext,qualScopePart);
410         if (!tmp.isEmpty()) qualScopePart=tmp;
411         resScope = resScope->findInnerCompound(qualScopePart);
412         //printf("qualScopePart='%s' resScope=%p\n",qPrint(qualScopePart),resScope);
413         if (resScope==0) break;
414         ps=is+l;
415       }
416     }
417     //printf("resScope=%s\n",resScope? qPrint(resScope->name()) : "<none>");
418 
419     // step 2: get the member
420     if (resScope) // no scope or scope found in the current context
421     {
422       //printf("scope found: %s, look for typedef %s\n",
423       //     qPrint(resScope->qualifiedName()),qPrint(resName));
424       MemberNameLinkedMap *mnd=0;
425       if (resScope->definitionType()==Definition::TypeClass)
426       {
427         mnd=Doxygen::memberNameLinkedMap;
428       }
429       else
430       {
431         mnd=Doxygen::functionNameLinkedMap;
432       }
433       MemberName *mn=mnd->find(resName);
434       if (mn)
435       {
436         int minDist=-1;
437         for (const auto &tmd_p : *mn)
438         {
439           const MemberDef *tmd = tmd_p.get();
440           //printf("Found member %s resScope=%s outerScope=%s mContext=%p\n",
441           //    qPrint(tmd->name()),qPrint( resScope->name()),
442           //    qPrint(tmd->getOuterScope()->name()), mContext);
443           if (tmd->isTypedef() /*&& tmd->getOuterScope()==resScope*/)
444           {
445             SymbolResolver resolver;
446             int dist=resolver.isAccessibleFrom(resScope,tmd);
447             if (dist!=-1 && (md==0 || dist<minDist))
448             {
449               md = tmd;
450               minDist = dist;
451             }
452           }
453         }
454       }
455     }
456     mContext=mContext->getOuterScope();
457   }
458 
459   // step 3: get the member's type
460   if (md)
461   {
462     //printf(">>resolveTypeDef: Found typedef name '%s' in scope '%s' value='%s' args='%s'\n",
463     //    qPrint(qualifiedName),qPrint(context->name()),md->typeString(),md->argsString()
464     //    );
465     result=md->typeString();
466     QCString args = md->argsString();
467     if (args.find(")(")!=-1) // typedef of a function/member pointer
468     {
469       result+=args;
470     }
471     else if (args.find('[')!=-1) // typedef of an array
472     {
473       result+=args;
474     }
475     if (typedefContext) *typedefContext=md->getOuterScope();
476   }
477   else
478   {
479     //printf(">>resolveTypeDef: Typedef '%s' not found in scope '%s'!\n",
480     //    qPrint(qualifiedName),context ? qPrint(context->name()) : "<global>");
481   }
482   return result;
483 
484 }
485 
computeQualifiedIndex(const QCString & name)486 int computeQualifiedIndex(const QCString &name)
487 {
488   int i = name.find('<');
489   return name.findRev("::",i==-1 ? name.length() : i);
490 }
491 
492 //-------------------------------------------------------------------------
493 //-------------------------------------------------------------------------
494 //-------------------------------------------------------------------------
495 //-------------------------------------------------------------------------
496 
497 static const char constScope[]    = { 'c', 'o', 'n', 's', 't', ':' };
498 static const char volatileScope[] = { 'v', 'o', 'l', 'a', 't', 'i', 'l', 'e', ':' };
499 static const char virtualScope[]  = { 'v', 'i', 'r', 't', 'u', 'a', 'l', ':' };
500 static const char operatorScope[] = { 'o', 'p', 'e', 'r', 'a', 't', 'o', 'r', '?', '?', '?' };
501 
502 struct CharAroundSpace
503 {
CharAroundSpaceCharAroundSpace504   CharAroundSpace()
505   {
506     charMap[static_cast<int>('(')].before=FALSE;
507     charMap[static_cast<int>('=')].before=FALSE;
508     charMap[static_cast<int>('&')].before=FALSE;
509     charMap[static_cast<int>('*')].before=FALSE;
510     charMap[static_cast<int>('[')].before=FALSE;
511     charMap[static_cast<int>('|')].before=FALSE;
512     charMap[static_cast<int>('+')].before=FALSE;
513     charMap[static_cast<int>(';')].before=FALSE;
514     charMap[static_cast<int>(':')].before=FALSE;
515     charMap[static_cast<int>('/')].before=FALSE;
516 
517     charMap[static_cast<int>('=')].after=FALSE;
518     charMap[static_cast<int>(' ')].after=FALSE;
519     charMap[static_cast<int>('[')].after=FALSE;
520     charMap[static_cast<int>(']')].after=FALSE;
521     charMap[static_cast<int>('\t')].after=FALSE;
522     charMap[static_cast<int>('\n')].after=FALSE;
523     charMap[static_cast<int>(')')].after=FALSE;
524     charMap[static_cast<int>(',')].after=FALSE;
525     charMap[static_cast<int>('<')].after=FALSE;
526     charMap[static_cast<int>('|')].after=FALSE;
527     charMap[static_cast<int>('+')].after=FALSE;
528     charMap[static_cast<int>('(')].after=FALSE;
529     charMap[static_cast<int>('/')].after=FALSE;
530   }
531   struct CharElem
532   {
CharElemCharAroundSpace::CharElem533     CharElem() : before(TRUE), after(TRUE) {}
534     bool before;
535     bool after;
536   };
537 
538   CharElem charMap[256];
539 };
540 
541 static CharAroundSpace g_charAroundSpace;
542 
543 // Note: this function is not reentrant due to the use of static buffer!
removeRedundantWhiteSpace(const QCString & s)544 QCString removeRedundantWhiteSpace(const QCString &s)
545 {
546   bool cliSupport = Config_getBool(CPP_CLI_SUPPORT);
547   bool vhdl = Config_getBool(OPTIMIZE_OUTPUT_VHDL);
548 
549   if (s.isEmpty() || vhdl) return s;
550 
551   // We use a static character array to
552   // improve the performance of this function
553   // and thread_local is needed to make it multi-thread safe
554   static THREAD_LOCAL char *growBuf = 0;
555   static THREAD_LOCAL int growBufLen = 0;
556   if ((int)s.length()*3>growBufLen) // For input character we produce at most 3 output characters,
557   {
558     growBufLen = s.length()*3;
559     growBuf = (char *)realloc(growBuf,growBufLen+1); // add 1 for 0-terminator
560   }
561   if (growBuf==0) return s; // should not happen, only we run out of memory
562 
563   const char *src=s.data();
564   char *dst=growBuf;
565 
566   uint i=0;
567   uint l=s.length();
568   uint csp=0;
569   uint vosp=0;
570   uint vsp=0;
571   uint osp=0;
572   char c;
573   char pc=0;
574   // skip leading whitespace
575   while (i<l && isspace((uchar)src[i]))
576   {
577     i++;
578   }
579   for (;i<l;i++)
580   {
581     c=src[i];
582     char nc=i<l-1 ? src[i+1] : ' ';
583 
584     // search for "const"
585     if (csp<6 && c==constScope[csp] && // character matches substring "const"
586          (csp>0 ||                     // inside search string
587           i==0  ||                     // if it is the first character
588           !isId(pc)                    // the previous may not be a digit
589          )
590        )
591       csp++;
592     else // reset counter
593       csp=0;
594 
595     if (vosp<6 && c==volatileScope[vosp] && // character matches substring "volatile"
596          (vosp>0 ||                     // inside search string
597           i==0  ||                     // if it is the first character
598           !isId(pc)                    // the previous may not be a digit
599          )
600        )
601       vosp++;
602     else // reset counter
603       vosp=0;
604 
605     // search for "virtual"
606     if (vsp<8 && c==virtualScope[vsp] && // character matches substring "virtual"
607          (vsp>0 ||                       // inside search string
608           i==0  ||                       // if it is the first character
609           !isId(pc)                      // the previous may not be a digit
610          )
611        )
612       vsp++;
613     else // reset counter
614       vsp=0;
615 
616     // search for "operator"
617     if (osp<11 && (osp>=8 || c==operatorScope[osp]) && // character matches substring "operator" followed by 3 arbitrary characters
618         (osp>0 ||                         // inside search string
619          i==0 ||                          // if it is the first character
620          !isId(pc)                        // the previous may not be a digit
621         )
622        )
623       osp++;
624     else // reset counter
625       osp=0;
626 
627     switch(c)
628     {
629       case '"': // quoted string
630         {
631           *dst++=c;
632           pc = c;
633           i++;
634           for (;i<l;i++) // find end of string
635           {
636             c = src[i];
637             *dst++=c;
638             if (c=='\\' && i+1<l)
639             {
640               pc = c;
641               i++;
642               c = src[i];
643               *dst++=c;
644             }
645             else if (c=='"')
646             {
647               break;
648             }
649             pc = c;
650           }
651         }
652         break;
653       case '<': // current char is a <
654         *dst++=c;
655         if (i<l-1 &&
656             (isId(nc)) && // next char is an id char
657             (osp<8) // string in front is not "operator"
658            )
659         {
660           *dst++=' '; // add extra space
661         }
662         break;
663       case '>': // current char is a >
664         if (i>0 && !isspace((uchar)pc) &&
665             (isId(pc) || pc=='*' || pc=='&' || pc=='.' || pc=='>') && // prev char is an id char or space or *&.
666             (osp<8 || (osp==8 && pc!='-')) // string in front is not "operator>" or "operator->"
667            )
668         {
669           *dst++=' '; // add extra space in front
670         }
671         *dst++=c;
672         if (i<l-1 && (nc=='-' || nc=='&')) // '>-' -> '> -'
673         {
674           *dst++=' '; // add extra space after
675         }
676         break;
677       case ',': // current char is a ,
678         *dst++=c;
679         if (i>0 && !isspace((uchar)pc) &&
680             ((i<l-1 && (isId(nc) || nc=='[')) || // the [ is for attributes (see bug702170)
681              (i<l-2 && nc=='$' && isId(src[i+2])) ||   // for PHP: ',$name' -> ', $name'
682              (i<l-3 && nc=='&' && src[i+2]=='$' && isId(src[i+3])) // for PHP: ',&$name' -> ', &$name'
683             )
684            )
685         {
686           *dst++=' '; // add extra space after
687         }
688         break;
689       case '^':  // CLI 'Type^name' -> 'Type^ name'
690       case '%':  // CLI 'Type%name' -> 'Type% name'
691         *dst++=c;
692         if (cliSupport && i<l-1 && (isId(nc) || nc=='-'))
693         {
694           *dst++=' '; // add extra space after
695         }
696         break;
697       case ')':  // current char is a )  -> ')name' -> ') name'
698         *dst++=c;
699         if (i<l-1 && (isId(nc) || nc=='-'))
700         {
701           *dst++=' '; // add extra space after
702         }
703         break;
704       case '*':
705         if (i>0 && pc!=' ' && pc!='\t' && pc!=':' &&
706                    pc!='*' && pc!='&'  && pc!='(' && pc!='/' &&
707                    pc!='.' && osp<9
708            )
709           // avoid splitting &&, **, .*, operator*, operator->*
710         {
711           *dst++=' ';
712         }
713         *dst++=c;
714         break;
715       case '&':
716         if (i>0 && isId(pc) && osp<9)
717         {
718           if (nc != '=')
719           // avoid splitting operator&=
720           {
721             *dst++=' ';
722           }
723         }
724         *dst++=c;
725         break;
726       case '$':  // '$name' -> ' $name'
727                  // 'name$name' -> 'name$name'
728         if (isId(pc))
729         {
730           *dst++=c;
731           break;
732         }
733         // else fallthrough
734       case '@':  // '@name' -> ' @name'
735       case '\'': // ''name' -> '' name'
736         if (i>0 && i<l-1 && pc!='=' && pc!=':' && !isspace((uchar)pc) &&
737             isId(nc) && osp<8) // ")id" -> ") id"
738         {
739           *dst++=' ';
740         }
741         *dst++=c;
742         break;
743       case ':': // current char is a :
744         if (csp==6) // replace const::A by const ::A
745         {
746           *dst++=' ';
747           csp=0;
748         }
749         else if (vosp==9) // replace volatile::A by volatile ::A
750         {
751           *dst++=' ';
752           vosp=0;
753         }
754         else if (vsp==8) // replace virtual::A by virtual ::A
755         {
756           *dst++=' ';
757           vsp=0;
758         }
759         *dst++=c;
760         break;
761       case ' ':  // fallthrough
762       case '\n': // fallthrough
763       case '\t':
764         {
765           if (g_charAroundSpace.charMap[(uchar)pc].before &&
766               g_charAroundSpace.charMap[(uchar)nc].after  &&
767               !(pc==',' && nc=='.') &&
768               (osp<8 || (osp>=8 && isId(pc) && isId(nc)))
769                   // e.g.    'operator >>' -> 'operator>>',
770                   //         'operator "" _x' -> 'operator""_x',
771                   // but not 'operator int' -> 'operatorint'
772              )
773           { // keep space
774             *dst++=' ';
775           }
776           else if ((pc=='*' || pc=='&' || pc=='.') && nc=='>')
777           {
778             *dst++=' ';
779           }
780         }
781         break;
782       default:
783         *dst++=c;
784         if (c=='t' && csp==5 && i<l-1 && // found 't' in 'const'
785              !(isId(nc) || nc==')' || nc==',' || isspace((uchar)nc))
786            ) // prevent const ::A from being converted to const::A
787         {
788           *dst++=' ';
789           csp=0;
790         }
791         else if (c=='e' && vosp==8 && i<l-1 && // found 'e' in 'volatile'
792              !(isId(nc) || nc==')' || nc==',' || isspace((uchar)nc))
793            ) // prevent volatile ::A from being converted to volatile::A
794         {
795           *dst++=' ';
796           vosp=0;
797         }
798         else if (c=='l' && vsp==7 && i<l-1 && // found 'l' in 'virtual'
799              !(isId(nc) || nc==')' || nc==',' || isspace((uchar)nc))
800             ) // prevent virtual ::A from being converted to virtual::A
801         {
802           *dst++=' ';
803           vsp=0;
804         }
805         break;
806     }
807     pc=c;
808   }
809   *dst++='\0';
810   //printf("removeRedundantWhitespace(%s)->%s\n",qPrint(s),growBuf);
811   return growBuf;
812 }
813 
814 /**
815  * Returns the position in the string where a function parameter list
816  * begins, or -1 if one is not found.
817  */
findParameterList(const QCString & name)818 int findParameterList(const QCString &name)
819 {
820   int pos=-1;
821   int templateDepth=0;
822   do
823   {
824     if (templateDepth > 0)
825     {
826       int nextOpenPos=name.findRev('>', pos);
827       int nextClosePos=name.findRev('<', pos);
828       if (nextOpenPos!=-1 && nextOpenPos>nextClosePos)
829       {
830         ++templateDepth;
831         pos=nextOpenPos-1;
832       }
833       else if (nextClosePos!=-1)
834       {
835         --templateDepth;
836         pos=nextClosePos-1;
837       }
838       else // more >'s than <'s, see bug701295
839       {
840         return -1;
841       }
842     }
843     else
844     {
845       int lastAnglePos=name.findRev('>', pos);
846       int bracePos=name.findRev('(', pos);
847       if (lastAnglePos!=-1 && lastAnglePos>bracePos)
848       {
849         ++templateDepth;
850         pos=lastAnglePos-1;
851       }
852       else
853       {
854         int bp = bracePos>0 ? name.findRev('(',bracePos-1) : -1;
855         // bp test is to allow foo(int(&)[10]), but we need to make an exception for operator()
856         return bp==-1 || (bp>=8 && name.mid(bp-8,10)=="operator()") ? bracePos : bp;
857       }
858     }
859   } while (pos!=-1);
860   return -1;
861 }
862 
rightScopeMatch(const QCString & scope,const QCString & name)863 bool rightScopeMatch(const QCString &scope, const QCString &name)
864 {
865   int sl=scope.length();
866   int nl=name.length();
867   return (name==scope || // equal
868           (scope.right(nl)==name && // substring
869            sl-nl>1 && scope.at(sl-nl-1)==':' && scope.at(sl-nl-2)==':' // scope
870           )
871          );
872 }
873 
leftScopeMatch(const QCString & scope,const QCString & name)874 bool leftScopeMatch(const QCString &scope, const QCString &name)
875 {
876   int sl=scope.length();
877   int nl=name.length();
878   return (name==scope || // equal
879           (scope.left(nl)==name && // substring
880            sl>nl+1 && scope.at(nl)==':' && scope.at(nl+1)==':' // scope
881           )
882          );
883 }
884 
885 
linkifyText(const TextGeneratorIntf & out,const Definition * scope,const FileDef * fileScope,const Definition * self,const QCString & text,bool autoBreak,bool external,bool keepSpaces,int indentLevel)886 void linkifyText(const TextGeneratorIntf &out, const Definition *scope,
887     const FileDef *fileScope,const Definition *self,
888     const QCString &text, bool autoBreak,bool external,
889     bool keepSpaces,int indentLevel)
890 {
891   if (text.isEmpty()) return;
892   //printf("linkify='%s'\n",qPrint(text));
893   std::string txtStr=text.str();
894   size_t strLen = txtStr.length();
895   if (strLen==0) return;
896 
897   static const reg::Ex regExp(R"((::)?\a[\w~!\\.:$]*)");
898   reg::Iterator it(txtStr,regExp);
899   reg::Iterator end;
900 
901   //printf("linkifyText scope=%s fileScope=%s strtxt=%s strlen=%d external=%d\n",
902   //    scope ? qPrint(scope->name()):"<none>",
903   //    fileScope ? qPrint(fileScope->name()) : "<none>",
904   //    qPrint(txtStr),strLen,external);
905   size_t index=0;
906   size_t skipIndex=0;
907   size_t floatingIndex=0;
908   for (; it!=end ; ++it) // for each word from the text string
909   {
910     const auto &match = *it;
911     size_t newIndex = match.position();
912     size_t matchLen = match.length();
913     floatingIndex+=newIndex-skipIndex+matchLen;
914     if (newIndex>0 && txtStr.at(newIndex-1)=='0') // ignore hex numbers (match x00 in 0x00)
915     {
916       std::string part = txtStr.substr(skipIndex,newIndex+matchLen-skipIndex);
917       out.writeString(part.c_str(),keepSpaces);
918       skipIndex=index=newIndex+matchLen;
919       continue;
920     }
921 
922     // add non-word part to the result
923     bool insideString=FALSE;
924     for (size_t i=index;i<newIndex;i++)
925     {
926       if (txtStr.at(i)=='"') insideString=!insideString;
927     }
928 
929     //printf("floatingIndex=%d strlen=%d autoBreak=%d\n",floatingIndex,strLen,autoBreak);
930     if (strLen>35 && floatingIndex>30 && autoBreak) // try to insert a split point
931     {
932       std::string splitText = txtStr.substr(skipIndex,newIndex-skipIndex);
933       size_t splitLength = splitText.length();
934       size_t offset=1;
935       size_t i = splitText.find(',');
936       if (i==std::string::npos) { i=splitText.find('<'); if (i!=std::string::npos) offset=0; }
937       if (i==std::string::npos) i=splitText.find('>');
938       if (i==std::string::npos) i=splitText.find(' ');
939       //printf("splitText=[%s] len=%d i=%d offset=%d\n",qPrint(splitText),splitLength,i,offset);
940       if (i!=std::string::npos) // add a link-break at i in case of Html output
941       {
942         std::string part1 = splitText.substr(0,i+offset);
943         out.writeString(part1.c_str(),keepSpaces);
944         out.writeBreak(indentLevel==0 ? 0 : indentLevel+1);
945         std::string part2 = splitText.substr(i+offset);
946         out.writeString(part2.c_str(),keepSpaces);
947         floatingIndex=splitLength-i-offset+matchLen;
948       }
949       else
950       {
951         out.writeString(splitText.c_str(),keepSpaces);
952       }
953     }
954     else
955     {
956       //ol.docify(txtStr.mid(skipIndex,newIndex-skipIndex));
957       std::string part = txtStr.substr(skipIndex,newIndex-skipIndex);
958       out.writeString(part.c_str(),keepSpaces);
959     }
960     // get word from string
961     std::string word=txtStr.substr(newIndex,matchLen);
962     QCString matchWord = substitute(substitute(QCString(word),"\\","::"),".","::");
963     //printf("linkifyText word=%s matchWord=%s scope=%s\n",
964     //    qPrint(word),qPrint(matchWord),scope ? qPrint(scope->name()) : "<none>");
965     bool found=FALSE;
966     if (!insideString)
967     {
968       const MemberDef    *md=0;
969       const ClassDef     *cd=0;
970       const FileDef      *fd=0;
971       const NamespaceDef *nd=0;
972       const GroupDef     *gd=0;
973       const ConceptDef   *cnd=0;
974       //printf("** Match word '%s'\n",qPrint(matchWord));
975 
976       SymbolResolver resolver(fileScope);
977       cd=resolver.resolveClass(scope,matchWord);
978       const MemberDef *typeDef = resolver.getTypedef();
979       if (typeDef) // First look at typedef then class, see bug 584184.
980       {
981         //printf("Found typedef %s\n",qPrint(typeDef->name()));
982         if (external ? typeDef->isLinkable() : typeDef->isLinkableInProject())
983         {
984           if (typeDef->getOuterScope()!=self)
985           {
986             out.writeLink(typeDef->getReference(),
987                 typeDef->getOutputFileBase(),
988                 typeDef->anchor(),
989                 word.c_str());
990             found=TRUE;
991           }
992         }
993       }
994       if (!found && (cd || (cd=getClass(matchWord))))
995       {
996         //printf("Found class %s\n",qPrint(cd->name()));
997         // add link to the result
998         if (external ? cd->isLinkable() : cd->isLinkableInProject())
999         {
1000           if (cd!=self)
1001           {
1002             out.writeLink(cd->getReference(),cd->getOutputFileBase(),cd->anchor(),word.c_str());
1003             found=TRUE;
1004           }
1005         }
1006       }
1007       else if ((cd=getClass(matchWord+"-p"))) // search for Obj-C protocols as well
1008       {
1009         // add link to the result
1010         if (external ? cd->isLinkable() : cd->isLinkableInProject())
1011         {
1012           if (cd!=self)
1013           {
1014             out.writeLink(cd->getReference(),cd->getOutputFileBase(),cd->anchor(),word.c_str());
1015             found=TRUE;
1016           }
1017         }
1018       }
1019       else if ((cnd=getConcept(matchWord)))
1020       {
1021         // add link to the result
1022         if (external ? cnd->isLinkable() : cnd->isLinkableInProject())
1023         {
1024           if (cnd!=self)
1025           {
1026             out.writeLink(cnd->getReference(),cnd->getOutputFileBase(),cnd->anchor(),word.c_str());
1027             found=TRUE;
1028           }
1029         }
1030       }
1031       else
1032       {
1033         //printf("   -> nothing\n");
1034       }
1035 
1036       int m = matchWord.findRev("::");
1037       QCString scopeName;
1038       if (scope &&
1039           (scope->definitionType()==Definition::TypeClass ||
1040            scope->definitionType()==Definition::TypeNamespace
1041           )
1042          )
1043       {
1044         scopeName=scope->name();
1045       }
1046       else if (m!=-1)
1047       {
1048         scopeName = matchWord.left(m);
1049         matchWord = matchWord.mid(m+2);
1050       }
1051 
1052       //printf("ScopeName=%s\n",qPrint(scopeName));
1053       //if (!found) printf("Trying to link %s in %s\n",qPrint(word),qPrint(scopeName));
1054       if (!found &&
1055           getDefs(scopeName,matchWord,QCString(),md,cd,fd,nd,gd) &&
1056           //(md->isTypedef() || md->isEnumerate() ||
1057           // md->isReference() || md->isVariable()
1058           //) &&
1059           (external ? md->isLinkable() : md->isLinkableInProject())
1060          )
1061       {
1062         //printf("Found ref scope=%s\n",d ? qPrint(d->name()) : "<global>");
1063         //ol.writeObjectLink(d->getReference(),d->getOutputFileBase(),
1064         //                       md->anchor(),word);
1065         if (md!=self && (self==0 || md->name()!=self->name()))
1066           // name check is needed for overloaded members, where getDefs just returns one
1067         {
1068           /* in case of Fortran scop and the variable is a non Fortran variable: don't link,
1069            * see also getLink in fortrancode.l
1070            */
1071           if (!(scope && (scope->getLanguage() == SrcLangExt_Fortran) && md->isVariable() && (md->getLanguage() != SrcLangExt_Fortran)))
1072           {
1073             out.writeLink(md->getReference(),md->getOutputFileBase(),
1074                 md->anchor(),word.c_str());
1075             //printf("found symbol %s\n",qPrint(matchWord));
1076             found=TRUE;
1077           }
1078         }
1079       }
1080     }
1081 
1082     if (!found) // add word to the result
1083     {
1084       out.writeString(word.c_str(),keepSpaces);
1085     }
1086     // set next start point in the string
1087     //printf("index=%d/%d\n",index,txtStr.length());
1088     skipIndex=index=newIndex+matchLen;
1089   }
1090   // add last part of the string to the result.
1091   //ol.docify(txtStr.right(txtStr.length()-skipIndex));
1092   std::string lastPart = txtStr.substr(skipIndex);
1093   out.writeString(lastPart.c_str(),keepSpaces);
1094 }
1095 
writeMarkerList(OutputList & ol,const std::string & markerText,size_t numMarkers,std::function<void (size_t)> replaceFunc)1096 void writeMarkerList(OutputList &ol,const std::string &markerText,size_t numMarkers,
1097                      std::function<void(size_t)> replaceFunc)
1098 {
1099   static const reg::Ex marker(R"(@(\d+))");
1100   reg::Iterator it(markerText,marker);
1101   reg::Iterator end;
1102   size_t index=0;
1103   // now replace all markers in inheritLine with links to the classes
1104   for ( ; it!=end ; ++it)
1105   {
1106     const auto &match = *it;
1107     size_t newIndex = match.position();
1108     size_t matchLen = match.length();
1109     ol.parseText(markerText.substr(index,newIndex-index));
1110     unsigned long entryIndex = std::stoul(match[1].str());
1111     if (entryIndex<(unsigned long)numMarkers)
1112     {
1113       replaceFunc(entryIndex);
1114     }
1115     index=newIndex+matchLen;
1116   }
1117   ol.parseText(markerText.substr(index));
1118 }
1119 
writeExamples(OutputList & ol,const ExampleList & list)1120 void writeExamples(OutputList &ol,const ExampleList &list)
1121 {
1122   auto replaceFunc = [&list,&ol](size_t entryIndex)
1123   {
1124     const auto &e = list[entryIndex];
1125     ol.pushGeneratorState();
1126     ol.disable(OutputGenerator::Latex);
1127     ol.disable(OutputGenerator::RTF);
1128     ol.disable(OutputGenerator::Docbook);
1129     // link for Html / man
1130     //printf("writeObjectLink(file=%s)\n",qPrint(e->file));
1131     ol.writeObjectLink(QCString(),e.file,e.anchor,e.name);
1132     ol.popGeneratorState();
1133 
1134     ol.pushGeneratorState();
1135     ol.disable(OutputGenerator::Man);
1136     ol.disable(OutputGenerator::Html);
1137     // link for Latex / pdf with anchor because the sources
1138     // are not hyperlinked (not possible with a verbatim environment).
1139     ol.writeObjectLink(QCString(),e.file,QCString(),e.name);
1140     ol.popGeneratorState();
1141   };
1142 
1143   writeMarkerList(ol, theTranslator->trWriteList((int)list.size()).str(), list.size(), replaceFunc);
1144 
1145   ol.writeString(".");
1146 }
1147 
1148 
argListToString(const ArgumentList & al,bool useCanonicalType,bool showDefVals)1149 QCString argListToString(const ArgumentList &al,bool useCanonicalType,bool showDefVals)
1150 {
1151   QCString result;
1152   if (!al.hasParameters()) return result;
1153   result+="(";
1154   for (auto it = al.begin() ; it!=al.end() ;)
1155   {
1156     Argument a = *it;
1157     QCString type1 = useCanonicalType && !a.canType.isEmpty() ? a.canType : a.type;
1158     QCString type2;
1159     int i=type1.find(")("); // hack to deal with function pointers
1160     if (i!=-1)
1161     {
1162       type2=type1.mid(i);
1163       type1=type1.left(i);
1164     }
1165     if (!a.attrib.isEmpty())
1166     {
1167       result+=a.attrib+" ";
1168     }
1169     if (!a.name.isEmpty() || !a.array.isEmpty())
1170     {
1171       result+= type1+" "+a.name+type2+a.array;
1172     }
1173     else
1174     {
1175       result+= type1+type2;
1176     }
1177     if (!a.defval.isEmpty() && showDefVals)
1178     {
1179       result+="="+a.defval;
1180     }
1181     ++it;
1182     if (it!=al.end()) result+=", ";
1183   }
1184   result+=")";
1185   if (al.constSpecifier()) result+=" const";
1186   if (al.volatileSpecifier()) result+=" volatile";
1187   if (al.refQualifier()==RefQualifierLValue) result+=" &";
1188   else if (al.refQualifier()==RefQualifierRValue) result+=" &&";
1189   if (!al.trailingReturnType().isEmpty()) result+=al.trailingReturnType();
1190   if (al.pureSpecifier()) result+=" =0";
1191   return removeRedundantWhiteSpace(result);
1192 }
1193 
tempArgListToString(const ArgumentList & al,SrcLangExt lang,bool includeDefault)1194 QCString tempArgListToString(const ArgumentList &al,SrcLangExt lang,bool includeDefault)
1195 {
1196   QCString result;
1197   if (al.empty()) return result;
1198   result="<";
1199   bool first=true;
1200   for (const auto &a : al)
1201   {
1202     if (a.defval.isEmpty() || includeDefault)
1203     {
1204       if (!first) result+=", ";
1205       if (!a.name.isEmpty()) // add template argument name
1206       {
1207         if (lang==SrcLangExt_Java || lang==SrcLangExt_CSharp)
1208         {
1209           result+=a.type+" ";
1210         }
1211         result+=a.name;
1212       }
1213       else // extract name from type
1214       {
1215         int i=a.type.length()-1;
1216         while (i>=0 && isId(a.type.at(i))) i--;
1217         if (i>0)
1218         {
1219           result+=a.type.right(a.type.length()-i-1);
1220           if (a.type.find("...")!=-1)
1221           {
1222             result+="...";
1223           }
1224         }
1225         else // nothing found -> take whole name
1226         {
1227           result+=a.type;
1228         }
1229       }
1230       if (!a.typeConstraint.isEmpty() && lang==SrcLangExt_Java)
1231       {
1232         result+=" extends "; // TODO: now Java specific, C# has where...
1233         result+=a.typeConstraint;
1234       }
1235       first=false;
1236     }
1237   }
1238   result+=">";
1239   return removeRedundantWhiteSpace(result);
1240 }
1241 
1242 
1243 //----------------------------------------------------------------------------
1244 
1245 /*! takes the \a buf of the given length \a len and converts CR LF (DOS)
1246  * or CR (MAC) line ending to LF (Unix).  Returns the length of the
1247  * converted content (i.e. the same as \a len (Unix, MAC) or
1248  * smaller (DOS).
1249  */
filterCRLF(char * buf,int len)1250 static int filterCRLF(char *buf,int len)
1251 {
1252   int src = 0;    // source index
1253   int dest = 0;   // destination index
1254   char c;         // current character
1255 
1256   while (src<len)
1257   {
1258     c = buf[src++];            // Remember the processed character.
1259     if (c == '\r')             // CR to be solved (MAC, DOS)
1260     {
1261       c = '\n';                // each CR to LF
1262       if (src<len && buf[src] == '\n')
1263         ++src;                 // skip LF just after CR (DOS)
1264     }
1265     else if ( c == '\0' && src<len-1) // filter out internal \0 characters, as it will confuse the parser
1266     {
1267       c = ' ';                 // turn into a space
1268     }
1269     buf[dest++] = c;           // copy the (modified) character to dest
1270   }
1271   return dest;                 // length of the valid part of the buf
1272 }
1273 
getFilterFromList(const QCString & name,const StringVector & filterList,bool & found)1274 static QCString getFilterFromList(const QCString &name,const StringVector &filterList,bool &found)
1275 {
1276   found=FALSE;
1277   // compare the file name to the filter pattern list
1278   for (const auto &filterStr : filterList)
1279   {
1280     QCString fs = filterStr.c_str();
1281     int i_equals=fs.find('=');
1282     if (i_equals!=-1)
1283     {
1284       QCString filterPattern = fs.left(i_equals);
1285       QCString input = name;
1286       if (!Portable::fileSystemIsCaseSensitive())
1287       {
1288         filterPattern = filterPattern.lower();
1289         input = input.lower();
1290       }
1291       reg::Ex re(filterPattern.str(),reg::Ex::Mode::Wildcard);
1292       if (re.isValid() && reg::match(input.str(),re))
1293       {
1294         // found a match!
1295         QCString filterName = fs.mid(i_equals+1);
1296         if (filterName.find(' ')!=-1)
1297         { // add quotes if the name has spaces
1298           filterName="\""+filterName+"\"";
1299         }
1300         found=TRUE;
1301         return filterName;
1302       }
1303     }
1304   }
1305 
1306   // no match
1307   return "";
1308 }
1309 
1310 /*! looks for a filter for the file \a name.  Returns the name of the filter
1311  *  if there is a match for the file name, otherwise an empty string.
1312  *  In case \a inSourceCode is TRUE then first the source filter list is
1313  *  considered.
1314  */
getFileFilter(const QCString & name,bool isSourceCode)1315 QCString getFileFilter(const QCString &name,bool isSourceCode)
1316 {
1317   // sanity check
1318   if (name.isEmpty()) return "";
1319 
1320   const StringVector& filterSrcList = Config_getList(FILTER_SOURCE_PATTERNS);
1321   const StringVector& filterList    = Config_getList(FILTER_PATTERNS);
1322 
1323   QCString filterName;
1324   bool found=FALSE;
1325   if (isSourceCode && !filterSrcList.empty())
1326   { // first look for source filter pattern list
1327     filterName = getFilterFromList(name,filterSrcList,found);
1328   }
1329   if (!found && filterName.isEmpty())
1330   { // then look for filter pattern list
1331     filterName = getFilterFromList(name,filterList,found);
1332   }
1333   if (!found)
1334   { // then use the generic input filter
1335     return Config_getString(INPUT_FILTER);
1336   }
1337   else
1338   {
1339     /* remove surrounding double quotes */
1340     if ((filterName.right(1) == "\"") && (filterName.left(1) == "\""))
1341     {
1342        filterName.remove(filterName.length() - 1, 1);
1343        filterName.remove(0, 1);
1344     }
1345     return filterName;
1346   }
1347 }
1348 
1349 
transcodeCharacterStringToUTF8(const QCString & input)1350 QCString transcodeCharacterStringToUTF8(const QCString &input)
1351 {
1352   bool error=FALSE;
1353   static QCString inputEncoding = Config_getString(INPUT_ENCODING);
1354   const char *outputEncoding = "UTF-8";
1355   if (inputEncoding.isEmpty() || qstricmp(inputEncoding,outputEncoding)==0) return input;
1356   int inputSize=input.length();
1357   int outputSize=inputSize*4+1;
1358   QCString output(outputSize);
1359   void *cd = portable_iconv_open(outputEncoding,inputEncoding.data());
1360   if (cd==(void *)(-1))
1361   {
1362     err("unsupported character conversion: '%s'->'%s'\n",
1363         qPrint(inputEncoding),outputEncoding);
1364     error=TRUE;
1365   }
1366   if (!error)
1367   {
1368     size_t iLeft=inputSize;
1369     size_t oLeft=outputSize;
1370     const char *inputPtr = input.data();
1371     char *outputPtr = output.rawData();
1372     if (!portable_iconv(cd, &inputPtr, &iLeft, &outputPtr, &oLeft))
1373     {
1374       outputSize-=(int)oLeft;
1375       output.resize(outputSize+1);
1376       output.at(outputSize)='\0';
1377       //printf("iconv: input size=%d output size=%d\n[%s]\n",size,newSize,qPrint(srcBuf));
1378     }
1379     else
1380     {
1381       err("failed to translate characters from %s to %s: check INPUT_ENCODING\ninput=[%s]\n",
1382           qPrint(inputEncoding),outputEncoding,qPrint(input));
1383       error=TRUE;
1384     }
1385   }
1386   portable_iconv_close(cd);
1387   return error ? input : output;
1388 }
1389 
1390 /*! reads a file with name \a name and returns it as a string. If \a filter
1391  *  is TRUE the file will be filtered by any user specified input filter.
1392  *  If \a name is "-" the string will be read from standard input.
1393  */
fileToString(const QCString & name,bool filter,bool isSourceCode)1394 QCString fileToString(const QCString &name,bool filter,bool isSourceCode)
1395 {
1396   if (name.isEmpty()) return QCString();
1397   bool fileOpened=false;
1398   if (name[0]=='-' && name[1]==0) // read from stdin
1399   {
1400     fileOpened=true;
1401     std::string contents;
1402     std::string line;
1403     while (getline(std::cin,line))
1404     {
1405       contents+=line+'\n';
1406     }
1407     return QCString(contents);
1408   }
1409   else // read from file
1410   {
1411     FileInfo fi(name.str());
1412     if (!fi.exists() || !fi.isFile())
1413     {
1414       err("file '%s' not found\n",qPrint(name));
1415       return "";
1416     }
1417     BufStr buf((uint)fi.size());
1418     fileOpened=readInputFile(name,buf,filter,isSourceCode);
1419     if (fileOpened)
1420     {
1421       int s = buf.size();
1422       if (s>1 && buf.at(s-2)!='\n')
1423       {
1424         buf.at(s-1)='\n';
1425         buf.addChar(0);
1426       }
1427       return buf.data();
1428     }
1429   }
1430   if (!fileOpened)
1431   {
1432     err("cannot open file '%s' for reading\n",qPrint(name));
1433   }
1434   return "";
1435 }
1436 
getCurrentDateTime()1437 static std::tm getCurrentDateTime()
1438 {
1439   QCString sourceDateEpoch = Portable::getenv("SOURCE_DATE_EPOCH");
1440   if (!sourceDateEpoch.isEmpty()) // see https://reproducible-builds.org/specs/source-date-epoch/
1441   {
1442     bool ok;
1443     uint64 epoch = sourceDateEpoch.toUInt64(&ok);
1444     if (!ok)
1445     {
1446       static bool warnedOnce=FALSE;
1447       if (!warnedOnce)
1448       {
1449         warn_uncond("Environment variable SOURCE_DATE_EPOCH does not contain a valid number; value is '%s'\n",
1450             qPrint(sourceDateEpoch));
1451         warnedOnce=TRUE;
1452       }
1453     }
1454     else // use given epoch value as current 'built' time
1455     {
1456       auto epoch_start    = std::chrono::time_point<std::chrono::system_clock>{};
1457       auto epoch_seconds  = std::chrono::seconds(epoch);
1458       auto build_time     = epoch_start + epoch_seconds;
1459       std::time_t time    = std::chrono::system_clock::to_time_t(build_time);
1460       return *gmtime(&time);
1461     }
1462   }
1463 
1464   // return current local time
1465   auto now = std::chrono::system_clock::now();
1466   std::time_t time = std::chrono::system_clock::to_time_t(now);
1467   return *localtime(&time);
1468 }
1469 
dateToString(bool includeTime)1470 QCString dateToString(bool includeTime)
1471 {
1472   auto current = getCurrentDateTime();
1473   return theTranslator->trDateTime(current.tm_year + 1900,
1474                                    current.tm_mon + 1,
1475                                    current.tm_mday,
1476                                    (current.tm_wday+6)%7+1, // map: Sun=0..Sat=6 to Mon=1..Sun=7
1477                                    current.tm_hour,
1478                                    current.tm_min,
1479                                    current.tm_sec,
1480                                    includeTime);
1481 }
1482 
yearToString()1483 QCString yearToString()
1484 {
1485   auto current = getCurrentDateTime();
1486   return QCString().setNum(current.tm_year+1900);
1487 }
1488 
trimBaseClassScope(const BaseClassList & bcl,QCString & s,int level=0)1489 void trimBaseClassScope(const BaseClassList &bcl,QCString &s,int level=0)
1490 {
1491   //printf("trimBaseClassScope level=%d '%s'\n",level,qPrint(s));
1492   for (const auto &bcd : bcl)
1493   {
1494     ClassDef *cd=bcd.classDef;
1495     //printf("Trying class %s\n",qPrint(cd->name()));
1496     int spos=s.find(cd->name()+"::");
1497     if (spos!=-1)
1498     {
1499       s = s.left(spos)+s.right(
1500           s.length()-spos-cd->name().length()-2
1501           );
1502     }
1503     //printf("base class '%s'\n",qPrint(cd->name()));
1504     if (!cd->baseClasses().empty())
1505     {
1506       trimBaseClassScope(cd->baseClasses(),s,level+1);
1507     }
1508   }
1509 }
1510 
stripIrrelevantString(QCString & target,const QCString & str)1511 static void stripIrrelevantString(QCString &target,const QCString &str)
1512 {
1513   if (target==str) { target.resize(0); return; }
1514   int i,p=0;
1515   int l=str.length();
1516   bool changed=FALSE;
1517   while ((i=target.find(str,p))!=-1)
1518   {
1519     bool isMatch = (i==0 || !isId(target.at(i-1))) && // not a character before str
1520       (i+l==(int)target.length() || !isId(target.at(i+l))); // not a character after str
1521     if (isMatch)
1522     {
1523       int i1=target.find('*',i+l);
1524       int i2=target.find('&',i+l);
1525       if (i1==-1 && i2==-1)
1526       {
1527         // strip str from target at index i
1528         target=target.left(i)+target.right(target.length()-i-l);
1529         changed=TRUE;
1530         i-=l;
1531       }
1532       else if ((i1!=-1 && i<i1) || (i2!=-1 && i<i2)) // str before * or &
1533       {
1534         // move str to front
1535         target=str+" "+target.left(i)+target.right(target.length()-i-l);
1536         changed=TRUE;
1537         i++;
1538       }
1539     }
1540     p = i+l;
1541   }
1542   if (changed) target=target.stripWhiteSpace();
1543 }
1544 
1545 /*! According to the C++ spec and Ivan Vecerina:
1546 
1547   Parameter declarations  that differ only in the presence or absence
1548   of const and/or volatile are equivalent.
1549 
1550   So the following example, show what is stripped by this routine
1551   for const. The same is done for volatile.
1552 
1553   For Java code we also strip the "final" keyword, see bug 765070.
1554 
1555   \code
1556   const T param     ->   T param          // not relevant
1557   const T& param    ->   const T& param   // const needed
1558   T* const param    ->   T* param         // not relevant
1559   const T* param    ->   const T* param   // const needed
1560   \endcode
1561  */
stripIrrelevantConstVolatile(QCString & s)1562 void stripIrrelevantConstVolatile(QCString &s)
1563 {
1564   //printf("stripIrrelevantConstVolatile(%s)=",qPrint(s));
1565   stripIrrelevantString(s,"const");
1566   stripIrrelevantString(s,"volatile");
1567   stripIrrelevantString(s,"final");
1568   //printf("%s\n",qPrint(s));
1569 }
1570 
1571 
1572 // a bit of debug support for matchArguments
1573 #define MATCH
1574 #define NOMATCH
1575 //#define MATCH printf("Match at line %d\n",__LINE__);
1576 //#define NOMATCH printf("Nomatch at line %d\n",__LINE__);
1577 
stripDeclKeywords(const QCString & s)1578 static QCString stripDeclKeywords(const QCString &s)
1579 {
1580   int i=s.find(" class ");
1581   if (i!=-1) return s.left(i)+s.mid(i+6);
1582   i=s.find(" typename ");
1583   if (i!=-1) return s.left(i)+s.mid(i+9);
1584   i=s.find(" union ");
1585   if (i!=-1) return s.left(i)+s.mid(i+6);
1586   i=s.find(" struct ");
1587   if (i!=-1) return s.left(i)+s.mid(i+7);
1588   return s;
1589 }
1590 
1591 // forward decl for circular dependencies
1592 static QCString extractCanonicalType(const Definition *d,const FileDef *fs,QCString type);
1593 
getCanonicalTemplateSpec(const Definition * d,const FileDef * fs,const QCString & spec)1594 QCString getCanonicalTemplateSpec(const Definition *d,const FileDef *fs,const QCString& spec)
1595 {
1596 
1597   QCString templSpec = spec.stripWhiteSpace();
1598   // this part had been commented out before... but it is needed to match for instance
1599   // std::list<std::string> against list<string> so it is now back again!
1600   if (!templSpec.isEmpty() && templSpec.at(0) == '<')
1601   {
1602     templSpec = "< " + extractCanonicalType(d,fs,templSpec.right(templSpec.length()-1).stripWhiteSpace());
1603   }
1604   QCString resolvedType = resolveTypeDef(d,templSpec);
1605   if (!resolvedType.isEmpty()) // not known as a typedef either
1606   {
1607     templSpec = resolvedType;
1608   }
1609   //printf("getCanonicalTemplateSpec(%s)=%s\n",qPrint(spec),qPrint(templSpec));
1610   return templSpec;
1611 }
1612 
1613 
getCanonicalTypeForIdentifier(const Definition * d,const FileDef * fs,const QCString & word,QCString * tSpec,int count=0)1614 static QCString getCanonicalTypeForIdentifier(
1615     const Definition *d,const FileDef *fs,const QCString &word,
1616     QCString *tSpec,int count=0)
1617 {
1618   if (count>10) return word; // oops recursion
1619 
1620   QCString symName,result,templSpec,tmpName;
1621   if (tSpec && !tSpec->isEmpty())
1622     templSpec = stripDeclKeywords(getCanonicalTemplateSpec(d,fs,*tSpec));
1623 
1624   if (word.findRev("::")!=-1 && !(tmpName=stripScope(word)).isEmpty())
1625   {
1626     symName=tmpName; // name without scope
1627   }
1628   else
1629   {
1630     symName=word;
1631   }
1632   //printf("getCanonicalTypeForIdentifier(%s d=%s fs=%s ,[%s->%s]) start\n",
1633   //    qPrint(word),
1634   //    d ? qPrint(d->name()) : "<null>", fs ? qPrint(fs->name()) : "<null>",
1635   //    tSpec ? qPrint(tSpec) : "<none>", qPrint(templSpec));
1636 
1637   // lookup class / class template instance
1638   SymbolResolver resolver(fs);
1639   const ClassDef *cd     = resolver.resolveClass(d,word+templSpec,true,true);
1640   const MemberDef *mType = resolver.getTypedef();
1641   QCString ts            = resolver.getTemplateSpec();
1642   QCString resolvedType  = resolver.getResolvedType();
1643 
1644   bool isTemplInst = cd && !templSpec.isEmpty();
1645   if (!cd && !templSpec.isEmpty())
1646   {
1647     // class template specialization not known, look up class template
1648     cd           = resolver.resolveClass(d,word,true,true);
1649     mType        = resolver.getTypedef();
1650     ts           = resolver.getTemplateSpec();
1651     resolvedType = resolver.getResolvedType();
1652   }
1653   if (cd && cd->isUsedOnly()) cd=0; // ignore types introduced by usage relations
1654 
1655   //printf("cd=%p mtype=%p\n",cd,mType);
1656   //printf("  getCanonicalTypeForIdentifier: symbol=%s word=%s cd=%s d=%s fs=%s cd->isTemplate=%d\n",
1657   //    qPrint(symName),
1658   //    qPrint(word),
1659   //    cd ? qPrint(cd->name()) : "<none>",
1660   //    d  ? qPrint( d->name()) : "<none>",
1661   //    fs ? qPrint(fs->name()) : "<none>",
1662   //    cd ? cd->isTemplate():-1
1663   //   );
1664 
1665   //printf("  >>>> word '%s' => '%s' templSpec=%s ts=%s tSpec=%s isTemplate=%d resolvedType=%s\n",
1666   //    qPrint((word+templSpec)),
1667   //    cd ? qPrint(cd->qualifiedName()) : "<none>",
1668   //    qPrint(templSpec), qPrint(ts),
1669   //    tSpec ? qPrint(tSpec) : "<null>",
1670   //    cd ? cd->isTemplate():FALSE,
1671   //    qPrint(resolvedType));
1672 
1673   //printf("  mtype=%s\n",mType ? qPrint(mType->name()) : "<none>");
1674 
1675   if (cd) // resolves to a known class type
1676   {
1677     if (cd==d && tSpec) *tSpec="";
1678 
1679     if (mType && mType->isTypedef()) // but via a typedef
1680     {
1681       result = resolvedType+ts; // the +ts was added for bug 685125
1682     }
1683     else
1684     {
1685       if (isTemplInst)
1686       {
1687         // spec is already part of class type
1688         templSpec="";
1689         if (tSpec) *tSpec="";
1690       }
1691       else if (!ts.isEmpty() && templSpec.isEmpty())
1692       {
1693         // use formal template args for spec
1694         templSpec = stripDeclKeywords(getCanonicalTemplateSpec(d,fs,ts));
1695       }
1696 
1697       result = removeRedundantWhiteSpace(cd->qualifiedName() + templSpec);
1698 
1699       if (cd->isTemplate() && tSpec) //
1700       {
1701         if (!templSpec.isEmpty()) // specific instance
1702         {
1703           result=cd->name()+templSpec;
1704         }
1705         else // use template type
1706         {
1707           result=cd->qualifiedNameWithTemplateParameters();
1708         }
1709         // template class, so remove the template part (it is part of the class name)
1710         *tSpec="";
1711       }
1712       else if (ts.isEmpty() && !templSpec.isEmpty() && cd && !cd->isTemplate() && tSpec)
1713       {
1714         // obscure case, where a class is used as a template, but doxygen think it is
1715         // not (could happen when loading the class from a tag file).
1716         *tSpec="";
1717       }
1718     }
1719   }
1720   else if (mType && mType->isEnumerate()) // an enum
1721   {
1722     result = mType->qualifiedName();
1723   }
1724   else if (mType && mType->isTypedef()) // a typedef
1725   {
1726     //result = mType->qualifiedName(); // changed after 1.7.2
1727     //result = mType->typeString();
1728     //printf("word=%s typeString=%s\n",qPrint(word),mType->typeString());
1729     if (word!=mType->typeString())
1730     {
1731       QCString type = mType->typeString();
1732       if (type.startsWith("typename "))
1733       {
1734         type.stripPrefix("typename ");
1735         type = stripTemplateSpecifiersFromScope(type,FALSE);
1736       }
1737       result = getCanonicalTypeForIdentifier(d,fs,type,tSpec,count+1);
1738     }
1739     else
1740     {
1741       result = mType->typeString();
1742     }
1743   }
1744   else // fallback
1745   {
1746     resolvedType = resolveTypeDef(d,word);
1747     //printf("typedef [%s]->[%s]\n",qPrint(word),qPrint(resolvedType));
1748     if (resolvedType.isEmpty()) // not known as a typedef either
1749     {
1750       result = word;
1751     }
1752     else
1753     {
1754       result = resolvedType;
1755     }
1756   }
1757   //printf("getCanonicalTypeForIdentifier [%s]->[%s]\n",qPrint(word),qPrint(result));
1758   return result;
1759 }
1760 
extractCanonicalType(const Definition * d,const FileDef * fs,QCString type)1761 static QCString extractCanonicalType(const Definition *d,const FileDef *fs,QCString type)
1762 {
1763   type = type.stripWhiteSpace();
1764 
1765   // strip const and volatile keywords that are not relevant for the type
1766   stripIrrelevantConstVolatile(type);
1767 
1768   // strip leading keywords
1769   type.stripPrefix("class ");
1770   type.stripPrefix("struct ");
1771   type.stripPrefix("union ");
1772   type.stripPrefix("enum ");
1773   type.stripPrefix("typename ");
1774 
1775   type = removeRedundantWhiteSpace(type);
1776   //printf("extractCanonicalType(type=%s) start: def=%s file=%s\n",qPrint(type),
1777   //    d ? qPrint(d->name()) : "<null>", fs ? qPrint(fs->name()) : "<null>");
1778 
1779   QCString canType;
1780   QCString templSpec,word;
1781   int i,p=0,pp=0;
1782   while ((i=extractClassNameFromType(type,p,word,templSpec))!=-1)
1783     // foreach identifier in the type
1784   {
1785     //printf("     i=%d p=%d\n",i,p);
1786     if (i>pp) canType += type.mid(pp,i-pp);
1787 
1788     QCString ct = getCanonicalTypeForIdentifier(d,fs,word,&templSpec);
1789 
1790     // in case the ct is empty it means that "word" represents scope "d"
1791     // and this does not need to be added to the canonical
1792     // type (it is redundant), so/ we skip it. This solves problem 589616.
1793     if (ct.isEmpty() && type.mid(p,2)=="::")
1794     {
1795       p+=2;
1796     }
1797     else
1798     {
1799       canType += ct;
1800     }
1801     //printf(" word=%s templSpec=%s canType=%s ct=%s\n",
1802     //    qPrint(word), qPrint(templSpec), qPrint(canType), qPrint(ct));
1803     if (!templSpec.isEmpty()) // if we didn't use up the templSpec already
1804                               // (i.e. type is not a template specialization)
1805                               // then resolve any identifiers inside.
1806     {
1807       std::string ts = templSpec.str();
1808       static const reg::Ex re(R"(\a\w*)");
1809       reg::Iterator it(ts,re);
1810       reg::Iterator end;
1811 
1812       size_t tp=0;
1813       // for each identifier template specifier
1814       //printf("adding resolved %s to %s\n",qPrint(templSpec),qPrint(canType));
1815       for (; it!=end ; ++it)
1816       {
1817         const auto &match = *it;
1818         size_t ti = match.position();
1819         size_t tl = match.length();
1820         std::string matchStr = match.str();
1821         canType += ts.substr(tp,ti-tp);
1822         canType += getCanonicalTypeForIdentifier(d,fs,matchStr.c_str(),0);
1823         tp=ti+tl;
1824       }
1825       canType+=ts.substr(tp);
1826     }
1827 
1828     pp=p;
1829   }
1830   canType += type.right(type.length()-pp);
1831   //printf("extractCanonicalType = '%s'->'%s'\n",qPrint(type),qPrint(canType));
1832 
1833   return removeRedundantWhiteSpace(canType);
1834 }
1835 
extractCanonicalArgType(const Definition * d,const FileDef * fs,const Argument & arg)1836 static QCString extractCanonicalArgType(const Definition *d,const FileDef *fs,const Argument &arg)
1837 {
1838   QCString type = arg.type.stripWhiteSpace();
1839   QCString name = arg.name;
1840   //printf("----- extractCanonicalArgType(type=%s,name=%s)\n",qPrint(type),qPrint(name));
1841   if ((type=="const" || type=="volatile") && !name.isEmpty())
1842   { // name is part of type => correct
1843     type+=" ";
1844     type+=name;
1845   }
1846   if (name=="const" || name=="volatile")
1847   { // name is part of type => correct
1848     if (!type.isEmpty()) type+=" ";
1849     type+=name;
1850   }
1851   if (!arg.array.isEmpty())
1852   {
1853     type+=arg.array;
1854   }
1855 
1856   return extractCanonicalType(d,fs,type);
1857 }
1858 
matchArgument2(const Definition * srcScope,const FileDef * srcFileScope,Argument & srcA,const Definition * dstScope,const FileDef * dstFileScope,Argument & dstA)1859 static bool matchArgument2(
1860     const Definition *srcScope,const FileDef *srcFileScope,Argument &srcA,
1861     const Definition *dstScope,const FileDef *dstFileScope,Argument &dstA
1862     )
1863 {
1864   //printf(">> match argument: %s::'%s|%s' (%s) <-> %s::'%s|%s' (%s)\n",
1865   //    srcScope ? qPrint(srcScope->name()) : "",
1866   //    qPrint(srcA.type), qPrint(srcA.name), qPrint(srcA.canType),
1867   //    dstScope ? qPrint(dstScope->name()) : "",
1868   //    qPrint(dstA.type), qPrint(dstA.name), qPrint(dstA.canType));
1869 
1870   //if (srcA->array!=dstA->array) // nomatch for char[] against char
1871   //{
1872   //  NOMATCH
1873   //  return FALSE;
1874   //}
1875   QCString sSrcName = " "+srcA.name;
1876   QCString sDstName = " "+dstA.name;
1877   QCString srcType  = srcA.type;
1878   QCString dstType  = dstA.type;
1879   stripIrrelevantConstVolatile(srcType);
1880   stripIrrelevantConstVolatile(dstType);
1881   //printf("'%s'<->'%s'\n",qPrint(sSrcName),qPrint(dstType.right(sSrcName.length())));
1882   //printf("'%s'<->'%s'\n",qPrint(sDstName),qPrint(srcType.right(sDstName.length())));
1883   if (sSrcName==dstType.right(sSrcName.length()))
1884   { // case "unsigned int" <-> "unsigned int i"
1885     srcA.type+=sSrcName;
1886     srcA.name="";
1887     srcA.canType=""; // invalidate cached type value
1888   }
1889   else if (sDstName==srcType.right(sDstName.length()))
1890   { // case "unsigned int i" <-> "unsigned int"
1891     dstA.type+=sDstName;
1892     dstA.name="";
1893     dstA.canType=""; // invalidate cached type value
1894   }
1895 
1896   if (srcA.canType.isEmpty() || dstA.canType.isEmpty())
1897   {
1898     // need to re-evaluate both see issue #8370
1899     srcA.canType = extractCanonicalArgType(srcScope,srcFileScope,srcA);
1900     dstA.canType = extractCanonicalArgType(dstScope,dstFileScope,dstA);
1901   }
1902 
1903   if (srcA.canType==dstA.canType)
1904   {
1905     MATCH
1906     return TRUE;
1907   }
1908   else
1909   {
1910     //printf("   Canonical types do not match [%s]<->[%s]\n",
1911     //    qPrint(srcA->canType),qPrint(dstA->canType));
1912     NOMATCH
1913     return FALSE;
1914   }
1915 }
1916 
1917 
1918 // new algorithm for argument matching
matchArguments2(const Definition * srcScope,const FileDef * srcFileScope,const ArgumentList * srcAl,const Definition * dstScope,const FileDef * dstFileScope,const ArgumentList * dstAl,bool checkCV)1919 bool matchArguments2(const Definition *srcScope,const FileDef *srcFileScope,const ArgumentList *srcAl,
1920                      const Definition *dstScope,const FileDef *dstFileScope,const ArgumentList *dstAl,
1921                      bool checkCV)
1922 {
1923   ASSERT(srcScope!=0 && dstScope!=0);
1924 
1925   if (srcAl==0 || dstAl==0)
1926   {
1927     bool match = srcAl==dstAl;
1928     if (match)
1929     {
1930       MATCH
1931       return TRUE;
1932     }
1933     else
1934     {
1935       NOMATCH
1936       return FALSE;
1937     }
1938   }
1939 
1940   // handle special case with void argument
1941   if ( srcAl->empty() && dstAl->size()==1 && dstAl->front().type=="void" )
1942   { // special case for finding match between func() and func(void)
1943     Argument a;
1944     a.type = "void";
1945     const_cast<ArgumentList*>(srcAl)->push_back(a);
1946     MATCH
1947     return TRUE;
1948   }
1949   if ( dstAl->empty() && srcAl->size()==1 && srcAl->front().type=="void" )
1950   { // special case for finding match between func(void) and func()
1951     Argument a;
1952     a.type = "void";
1953     const_cast<ArgumentList*>(dstAl)->push_back(a);
1954     MATCH
1955     return TRUE;
1956   }
1957 
1958   if (srcAl->size() != dstAl->size())
1959   {
1960     NOMATCH
1961     return FALSE; // different number of arguments -> no match
1962   }
1963 
1964   if (checkCV)
1965   {
1966     if (srcAl->constSpecifier() != dstAl->constSpecifier())
1967     {
1968       NOMATCH
1969       return FALSE; // one member is const, the other not -> no match
1970     }
1971     if (srcAl->volatileSpecifier() != dstAl->volatileSpecifier())
1972     {
1973       NOMATCH
1974       return FALSE; // one member is volatile, the other not -> no match
1975     }
1976   }
1977 
1978   if (srcAl->refQualifier() != dstAl->refQualifier())
1979   {
1980     NOMATCH
1981     return FALSE; // one member is has a different ref-qualifier than the other
1982   }
1983 
1984   // so far the argument list could match, so we need to compare the types of
1985   // all arguments.
1986   auto srcIt = srcAl->begin();
1987   auto dstIt = dstAl->begin();
1988   for (;srcIt!=srcAl->end() && dstIt!=dstAl->end();++srcIt,++dstIt)
1989   {
1990     Argument &srcA = const_cast<Argument&>(*srcIt);
1991     Argument &dstA = const_cast<Argument&>(*dstIt);
1992     if (!matchArgument2(srcScope,srcFileScope,srcA,
1993           dstScope,dstFileScope,dstA)
1994        )
1995     {
1996       NOMATCH
1997       return FALSE;
1998     }
1999   }
2000   MATCH
2001   return TRUE; // all arguments match
2002 }
2003 
2004 
2005 
2006 // merges the initializer of two argument lists
2007 // pre:  the types of the arguments in the list should match.
mergeArguments(ArgumentList & srcAl,ArgumentList & dstAl,bool forceNameOverwrite)2008 void mergeArguments(ArgumentList &srcAl,ArgumentList &dstAl,bool forceNameOverwrite)
2009 {
2010   //printf("mergeArguments '%s', '%s'\n",
2011   //    qPrint(argListToString(srcAl)),qPrint(argListToString(dstAl)));
2012 
2013   if (srcAl.size()!=dstAl.size())
2014   {
2015     return; // invalid argument lists -> do not merge
2016   }
2017 
2018   auto srcIt=srcAl.begin();
2019   auto dstIt=dstAl.begin();
2020   while (srcIt!=srcAl.end() && dstIt!=dstAl.end())
2021   {
2022     Argument &srcA = *srcIt;
2023     Argument &dstA = *dstIt;
2024 
2025     if (srcA.defval.isEmpty() && !dstA.defval.isEmpty())
2026     {
2027       //printf("Defval changing '%s'->'%s'\n",qPrint(srcA.defval),qPrint(dstA.defval));
2028       srcA.defval=dstA.defval;
2029     }
2030     else if (!srcA.defval.isEmpty() && dstA.defval.isEmpty())
2031     {
2032       //printf("Defval changing '%s'->'%s'\n",qPrint(dstA.defval),qPrint(srcA.defval));
2033       dstA.defval=srcA.defval;
2034     }
2035 
2036     // fix wrongly detected const or volatile specifiers before merging.
2037     // example: "const A *const" is detected as type="const A *" name="const"
2038     if (srcA.name=="const" || srcA.name=="volatile")
2039     {
2040       srcA.type+=" "+srcA.name;
2041       srcA.name.resize(0);
2042     }
2043     if (dstA.name=="const" || dstA.name=="volatile")
2044     {
2045       dstA.type+=" "+dstA.name;
2046       dstA.name.resize(0);
2047     }
2048 
2049     if (srcA.type==dstA.type)
2050     {
2051       //printf("1. merging %s:%s <-> %s:%s\n",qPrint(srcA.type),qPrint(srcA.name),qPrint(dstA.type),qPrint(dstA.name));
2052       if (srcA.name.isEmpty() && !dstA.name.isEmpty())
2053       {
2054         //printf("type: '%s':='%s'\n",qPrint(srcA.type),qPrint(dstA.type));
2055         //printf("name: '%s':='%s'\n",qPrint(srcA.name),qPrint(dstA.name));
2056         srcA.type = dstA.type;
2057         srcA.name = dstA.name;
2058       }
2059       else if (!srcA.name.isEmpty() && dstA.name.isEmpty())
2060       {
2061         //printf("type: '%s':='%s'\n",qPrint(dstA.type),qPrint(srcA.type));
2062         //printf("name: '%s':='%s'\n",qPrint(dstA.name),qPrint(srcA.name));
2063         dstA.type = srcA.type;
2064         dstA.name = srcA.name;
2065       }
2066       else if (!srcA.name.isEmpty() && !dstA.name.isEmpty())
2067       {
2068         //printf("srcA.name=%s dstA.name=%s\n",qPrint(srcA.name),qPrint(dstA.name));
2069         if (forceNameOverwrite)
2070         {
2071           srcA.name = dstA.name;
2072         }
2073         else
2074         {
2075           if (srcA.docs.isEmpty() && !dstA.docs.isEmpty())
2076           {
2077             srcA.name = dstA.name;
2078           }
2079           else if (!srcA.docs.isEmpty() && dstA.docs.isEmpty())
2080           {
2081             dstA.name = srcA.name;
2082           }
2083         }
2084       }
2085     }
2086     else
2087     {
2088       //printf("2. merging '%s':'%s' <-> '%s':'%s'\n",qPrint(srcA.type),qPrint(srcA.name),qPrint(dstA.type),qPrint(dstA.name));
2089       srcA.type=srcA.type.stripWhiteSpace();
2090       dstA.type=dstA.type.stripWhiteSpace();
2091       if (srcA.type+" "+srcA.name==dstA.type) // "unsigned long:int" <-> "unsigned long int:bla"
2092       {
2093         srcA.type+=" "+srcA.name;
2094         srcA.name=dstA.name;
2095       }
2096       else if (dstA.type+" "+dstA.name==srcA.type) // "unsigned long int bla" <-> "unsigned long int"
2097       {
2098         dstA.type+=" "+dstA.name;
2099         dstA.name=srcA.name;
2100       }
2101       else if (srcA.name.isEmpty() && !dstA.name.isEmpty())
2102       {
2103         srcA.name = dstA.name;
2104       }
2105       else if (dstA.name.isEmpty() && !srcA.name.isEmpty())
2106       {
2107         dstA.name = srcA.name;
2108       }
2109     }
2110     int i1=srcA.type.find("::"),
2111         i2=dstA.type.find("::"),
2112         j1=srcA.type.length()-i1-2,
2113         j2=dstA.type.length()-i2-2;
2114     if (i1!=-1 && i2==-1 && srcA.type.right(j1)==dstA.type)
2115     {
2116       //printf("type: '%s':='%s'\n",qPrint(dstA.type),qPrint(srcA.type));
2117       //printf("name: '%s':='%s'\n",qPrint(dstA.name),qPrint(srcA.name));
2118       dstA.type = srcA.type.left(i1+2)+dstA.type;
2119       dstA.name = srcA.name;
2120     }
2121     else if (i1==-1 && i2!=-1 && dstA.type.right(j2)==srcA.type)
2122     {
2123       //printf("type: '%s':='%s'\n",qPrint(srcA.type),qPrint(dstA.type));
2124       //printf("name: '%s':='%s'\n",qPrint(dstA.name),qPrint(srcA.name));
2125       srcA.type = dstA.type.left(i2+2)+srcA.type;
2126       srcA.name = dstA.name;
2127     }
2128     if (srcA.docs.isEmpty() && !dstA.docs.isEmpty())
2129     {
2130       srcA.docs = dstA.docs;
2131     }
2132     else if (dstA.docs.isEmpty() && !srcA.docs.isEmpty())
2133     {
2134       dstA.docs = srcA.docs;
2135     }
2136     //printf("Merge argument '%s|%s' '%s|%s'\n",
2137     //  qPrint(srcA.type), qPrint(srcA.name),
2138     //  qPrint(dstA.type), qPrint(dstA.name));
2139     ++srcIt;
2140     ++dstIt;
2141   }
2142 }
2143 
findMembersWithSpecificName(const MemberName * mn,const QCString & args,bool checkStatics,const FileDef * currentFile,bool checkCV,std::vector<const MemberDef * > & members)2144 static void findMembersWithSpecificName(const MemberName *mn,
2145                                         const QCString &args,
2146                                         bool checkStatics,
2147                                         const FileDef *currentFile,
2148                                         bool checkCV,
2149                                         std::vector<const MemberDef *> &members)
2150 {
2151   //printf("  Function with global scope name '%s' args='%s'\n",
2152   //       mn->memberName(),args);
2153   for (const auto &md_p : *mn)
2154   {
2155     const MemberDef *md = md_p.get();
2156     const FileDef  *fd=md->getFileDef();
2157     const GroupDef *gd=md->getGroupDef();
2158     //printf("  md->name()='%s' md->args='%s' fd=%p gd=%p current=%p ref=%s\n",
2159     //    qPrint(md->name()),args,fd,gd,currentFile,qPrint(md->getReference()));
2160     if (
2161         ((gd && gd->isLinkable()) || (fd && fd->isLinkable()) || md->isReference()) &&
2162         md->getNamespaceDef()==0 && md->isLinkable() &&
2163         (!checkStatics || (!md->isStatic() && !md->isDefine()) ||
2164          currentFile==0 || fd==currentFile) // statics must appear in the same file
2165        )
2166     {
2167       bool match=TRUE;
2168       if (!args.isEmpty() && !md->isDefine() && args!="()")
2169       {
2170         const ArgumentList &mdAl = md->argumentList();
2171         auto argList_p = stringToArgumentList(md->getLanguage(),args);
2172         match=matchArguments2(
2173             md->getOuterScope(),fd,&mdAl,
2174             Doxygen::globalScope,fd,argList_p.get(),
2175             checkCV);
2176       }
2177       if (match)
2178       {
2179         //printf("Found match!\n");
2180         members.push_back(md);
2181       }
2182     }
2183   }
2184 }
2185 
2186 /*!
2187  * Searches for a member definition given its name 'memberName' as a string.
2188  * memberName may also include a (partial) scope to indicate the scope
2189  * in which the member is located.
2190  *
2191  * The parameter 'scName' is a string representing the name of the scope in
2192  * which the link was found.
2193  *
2194  * In case of a function args contains a string representation of the
2195  * argument list. Passing 0 means the member has no arguments.
2196  * Passing "()" means any argument list will do, but "()" is preferred.
2197  *
2198  * The function returns TRUE if the member is known and documented or
2199  * FALSE if it is not.
2200  * If TRUE is returned parameter 'md' contains a pointer to the member
2201  * definition. Furthermore exactly one of the parameter 'cd', 'nd', or 'fd'
2202  * will be non-zero:
2203  *   - if 'cd' is non zero, the member was found in a class pointed to by cd.
2204  *   - if 'nd' is non zero, the member was found in a namespace pointed to by nd.
2205  *   - if 'fd' is non zero, the member was found in the global namespace of
2206  *     file fd.
2207  */
getDefs(const QCString & scName,const QCString & mbName,const QCString & args,const MemberDef * & md,const ClassDef * & cd,const FileDef * & fd,const NamespaceDef * & nd,const GroupDef * & gd,bool forceEmptyScope,const FileDef * currentFile,bool checkCV)2208 bool getDefs(const QCString &scName,
2209              const QCString &mbName,
2210              const QCString &args,
2211              const MemberDef *&md,
2212              const ClassDef *&cd,
2213              const FileDef *&fd,
2214              const NamespaceDef *&nd,
2215              const GroupDef *&gd,
2216              bool forceEmptyScope,
2217              const FileDef *currentFile,
2218              bool checkCV
2219             )
2220 {
2221   fd=0, md=0, cd=0, nd=0, gd=0;
2222   if (mbName.isEmpty()) return FALSE; /* empty name => nothing to link */
2223 
2224   QCString scopeName=scName;
2225   QCString memberName=mbName;
2226   scopeName = substitute(scopeName,"\\","::"); // for PHP
2227   memberName = substitute(memberName,"\\","::"); // for PHP
2228   //printf("Search for name=%s args=%s in scope=%s forceEmpty=%d\n",
2229   //          qPrint(memberName),qPrint(args),qPrint(scopeName),forceEmptyScope);
2230 
2231   int is,im=0,pm=0;
2232   // strip common part of the scope from the scopeName
2233   while ((is=scopeName.findRev("::"))!=-1 &&
2234          (im=memberName.find("::",pm))!=-1 &&
2235           (scopeName.right(scopeName.length()-is-2)==memberName.mid(pm,im-pm))
2236         )
2237   {
2238     scopeName=scopeName.left(is);
2239     pm=im+2;
2240   }
2241   //printf("result after scope corrections scope=%s name=%s\n",
2242   //          qPrint(scopeName), qPrint(memberName));
2243 
2244   QCString mName=memberName;
2245   QCString mScope;
2246   if (memberName.left(9)!="operator " && // treat operator conversion methods
2247       // as a special case
2248       (im=memberName.findRev("::"))!=-1 &&
2249       im<(int)memberName.length()-2 // not A::
2250      )
2251   {
2252     mScope=memberName.left(im);
2253     mName=memberName.right(memberName.length()-im-2);
2254   }
2255 
2256   // handle special the case where both scope name and member scope are equal
2257   if (mScope==scopeName) scopeName.resize(0);
2258 
2259   //printf("mScope='%s' mName='%s'\n",qPrint(mScope),qPrint(mName));
2260 
2261   MemberName *mn = Doxygen::memberNameLinkedMap->find(mName);
2262   //printf("mName=%s mn=%p\n",qPrint(mName),mn);
2263 
2264   if ((!forceEmptyScope || scopeName.isEmpty()) && // this was changed for bug638856, forceEmptyScope => empty scopeName
2265       mn && !(scopeName.isEmpty() && mScope.isEmpty()))
2266   {
2267     //printf("  >member name '%s' found\n",qPrint(mName));
2268     int scopeOffset=scopeName.length();
2269     do
2270     {
2271       QCString className = scopeName.left(scopeOffset);
2272       if (!className.isEmpty() && !mScope.isEmpty())
2273       {
2274         className+="::"+mScope;
2275       }
2276       else if (!mScope.isEmpty())
2277       {
2278         className=mScope;
2279       }
2280 
2281       SymbolResolver resolver;
2282       const ClassDef *fcd=resolver.resolveClass(Doxygen::globalScope,className);
2283       const MemberDef *tmd=resolver.getTypedef();
2284 
2285       if (fcd==0 && className.find('<')!=-1) // try without template specifiers as well
2286       {
2287          QCString nameWithoutTemplates = stripTemplateSpecifiersFromScope(className,FALSE);
2288          fcd=resolver.resolveClass(Doxygen::globalScope,nameWithoutTemplates);
2289          tmd=resolver.getTypedef();
2290       }
2291       //printf("Trying class scope %s: fcd=%p tmd=%p\n",qPrint(className),fcd,tmd);
2292       // todo: fill in correct fileScope!
2293       if (fcd &&  // is it a documented class
2294           fcd->isLinkable()
2295          )
2296       {
2297         //printf("  Found fcd=%p\n",fcd);
2298         int mdist=maxInheritanceDepth;
2299         std::unique_ptr<ArgumentList> argList;
2300         if (!args.isEmpty())
2301         {
2302           argList = stringToArgumentList(fcd->getLanguage(),args);
2303         }
2304         for (const auto &mmd_p : *mn)
2305         {
2306           MemberDef *mmd = mmd_p.get();
2307           if (!mmd->isStrongEnumValue())
2308           {
2309             const ArgumentList &mmdAl = mmd->argumentList();
2310             bool match = args.isEmpty() ||
2311               matchArguments2(mmd->getOuterScope(),mmd->getFileDef(),&mmdAl,
2312                              fcd,                  fcd->getFileDef(),argList.get(),
2313                              checkCV);
2314             //printf("match=%d\n",match);
2315             if (match)
2316             {
2317               const ClassDef *mcd=mmd->getClassDef();
2318               if (mcd)
2319               {
2320                 int m=minClassDistance(fcd,mcd);
2321                 if (m<mdist && mcd->isLinkable())
2322                 {
2323                   mdist=m;
2324                   cd=mcd;
2325                   md=mmd;
2326                 }
2327               }
2328             }
2329           }
2330         }
2331         if (mdist==maxInheritanceDepth && args=="()")
2332           // no exact match found, but if args="()" an arbitrary member will do
2333         {
2334           //printf("  >Searching for arbitrary member\n");
2335           for (const auto &mmd_p : *mn)
2336           {
2337             MemberDef *mmd = mmd_p.get();
2338             //if (mmd->isLinkable())
2339             //{
2340             const ClassDef *mcd=mmd->getClassDef();
2341             //printf("  >Class %s found\n",qPrint(mcd->name()));
2342             if (mcd)
2343             {
2344               int m=minClassDistance(fcd,mcd);
2345               if (m<mdist /* && mcd->isLinkable()*/ )
2346               {
2347                 //printf("Class distance %d\n",m);
2348                 mdist=m;
2349                 cd=mcd;
2350                 md=mmd;
2351               }
2352             }
2353             //}
2354           }
2355         }
2356         //printf("  >Success=%d\n",mdist<maxInheritanceDepth);
2357         if (mdist<maxInheritanceDepth)
2358         {
2359           if (!md->isLinkable() || md->isStrongEnumValue())
2360           {
2361             md=0; // avoid returning things we cannot link to
2362             cd=0;
2363             return FALSE; // match found, but was not linkable
2364           }
2365           else
2366           {
2367             gd=md->getGroupDef();
2368             if (gd) cd=0;
2369             return TRUE; /* found match */
2370           }
2371         }
2372       }
2373       if (tmd && tmd->isEnumerate() && tmd->isStrong()) // scoped enum
2374       {
2375         //printf("Found scoped enum!\n");
2376         for (const auto &emd : tmd->enumFieldList())
2377         {
2378           if (emd->localName()==mName)
2379           {
2380             if (emd->isLinkable())
2381             {
2382               cd=tmd->getClassDef();
2383               md=emd;
2384               return TRUE;
2385             }
2386             else
2387             {
2388               cd=0;
2389               md=0;
2390               return FALSE;
2391             }
2392           }
2393         }
2394       }
2395       /* go to the parent scope */
2396       if (scopeOffset==0)
2397       {
2398         scopeOffset=-1;
2399       }
2400       else if ((scopeOffset=scopeName.findRev("::",scopeOffset-1))==-1)
2401       {
2402         scopeOffset=0;
2403       }
2404     } while (scopeOffset>=0);
2405 
2406   }
2407   if (mn && scopeName.isEmpty() && mScope.isEmpty()) // Maybe a related function?
2408   {
2409     //printf("Global symbol\n");
2410     const MemberDef *fuzzy_mmd = 0;
2411     std::unique_ptr<ArgumentList> argList;
2412     bool hasEmptyArgs = args=="()";
2413 
2414     if (!args.isEmpty())
2415     {
2416       argList = stringToArgumentList(SrcLangExt_Cpp, args);
2417     }
2418 
2419     for (const auto &mmd_p : *mn)
2420     {
2421       const MemberDef *mmd = mmd_p.get();
2422       if (!mmd->isLinkable() || (!mmd->isRelated() && !mmd->isForeign()) ||
2423            !mmd->getClassDef())
2424       {
2425         continue;
2426       }
2427 
2428       if (args.isEmpty())
2429       {
2430         fuzzy_mmd = mmd;
2431         break;
2432       }
2433 
2434       const ArgumentList &mmdAl = mmd->argumentList();
2435       if (matchArguments2(mmd->getOuterScope(),mmd->getFileDef(),&mmdAl,
2436             Doxygen::globalScope,mmd->getFileDef(),argList.get(),
2437             checkCV
2438             )
2439          )
2440       {
2441         fuzzy_mmd = mmd;
2442         break;
2443       }
2444 
2445       if (!fuzzy_mmd && hasEmptyArgs)
2446       {
2447         fuzzy_mmd = mmd;
2448       }
2449     }
2450 
2451     if (fuzzy_mmd && !fuzzy_mmd->isStrongEnumValue())
2452     {
2453       md = fuzzy_mmd;
2454       cd = fuzzy_mmd->getClassDef();
2455       return TRUE;
2456     }
2457   }
2458 
2459 
2460   // maybe an namespace, file or group member ?
2461   //printf("Testing for global symbol scopeName='%s' mScope='%s' :: mName='%s'\n",
2462   //              qPrint(scopeName), qPrint(mScope), qPrint(mName));
2463   if ((mn=Doxygen::functionNameLinkedMap->find(mName))) // name is known
2464   {
2465     //printf("  >symbol name found\n");
2466     NamespaceDef *fnd=0;
2467     int scopeOffset=scopeName.length();
2468     do
2469     {
2470       QCString namespaceName = scopeName.left(scopeOffset);
2471       if (!namespaceName.isEmpty() && !mScope.isEmpty())
2472       {
2473         namespaceName+="::"+mScope;
2474       }
2475       else if (!mScope.isEmpty())
2476       {
2477         namespaceName=mScope;
2478       }
2479       //printf("Trying namespace %s\n",qPrint(namespaceName));
2480       if (!namespaceName.isEmpty() &&
2481           (fnd=Doxygen::namespaceLinkedMap->find(namespaceName)) &&
2482           fnd->isLinkable()
2483          )
2484       {
2485         //printf("Symbol inside existing namespace '%s' count=%d\n",
2486         //    qPrint(namespaceName),mn->count());
2487         bool found=FALSE;
2488         for (const auto &mmd_p : *mn)
2489         {
2490           const MemberDef *mmd = mmd_p.get();
2491           //printf("mmd->getNamespaceDef()=%p fnd=%p\n",
2492           //    mmd->getNamespaceDef(),fnd);
2493           const MemberDef *emd = mmd->getEnumScope();
2494           if (emd && emd->isStrong())
2495           {
2496             //printf("yes match %s<->%s!\n",qPrint(mScope),qPrint(emd->localName()));
2497             if (emd->getNamespaceDef()==fnd &&
2498                 rightScopeMatch(mScope,emd->localName()))
2499             {
2500               //printf("found it!\n");
2501               nd=fnd;
2502               md=mmd;
2503               found=TRUE;
2504               break;
2505             }
2506             else
2507             {
2508               md=0;
2509               cd=0;
2510               return FALSE;
2511             }
2512           }
2513           else if (mmd->getOuterScope()==fnd /* && mmd->isLinkable() */ )
2514           { // namespace is found
2515             bool match=TRUE;
2516             if (!args.isEmpty() && args!="()")
2517             {
2518               const ArgumentList &mmdAl = mmd->argumentList();
2519               auto argList_p = stringToArgumentList(mmd->getLanguage(),args);
2520               match=matchArguments2(
2521                   mmd->getOuterScope(),mmd->getFileDef(),&mmdAl,
2522                   fnd,mmd->getFileDef(),argList_p.get(),
2523                   checkCV);
2524             }
2525             if (match)
2526             {
2527               nd=fnd;
2528               md=mmd;
2529               found=TRUE;
2530               break;
2531             }
2532           }
2533         }
2534         if (!found && args=="()")
2535           // no exact match found, but if args="()" an arbitrary
2536           // member will do
2537         {
2538           for (const auto &mmd_p : *mn)
2539           {
2540             const MemberDef *mmd = mmd_p.get();
2541             if (mmd->getNamespaceDef()==fnd /*&& mmd->isLinkable() */ )
2542             {
2543               nd=fnd;
2544               md=mmd;
2545               found=TRUE;
2546               break;
2547             }
2548           }
2549         }
2550         if (found)
2551         {
2552           if (!md->isLinkable())
2553           {
2554             md=0; // avoid returning things we cannot link to
2555             nd=0;
2556             return FALSE; // match found but not linkable
2557           }
2558           else
2559           {
2560             gd=md->resolveAlias()->getGroupDef();
2561             if (gd && gd->isLinkable()) nd=0; else gd=0;
2562             return TRUE;
2563           }
2564         }
2565       }
2566       else
2567       {
2568         //printf("not a namespace\n");
2569         for (const auto &mmd_p : *mn)
2570         {
2571           const MemberDef *mmd = mmd_p.get();
2572           const MemberDef *tmd = mmd->getEnumScope();
2573           //printf("try member %s tmd=%s\n",qPrint(mmd->name()),tmd ? qPrint(tmd->name()) : "<none>");
2574           int ni=namespaceName.findRev("::");
2575           //printf("namespaceName=%s ni=%d\n",qPrint(namespaceName),ni);
2576           bool notInNS = tmd && ni==-1 && tmd->getNamespaceDef()==0 && (mScope.isEmpty() || mScope==tmd->name());
2577           bool sameNS  = tmd && ni>=0 && tmd->getNamespaceDef() && namespaceName.left(ni)==tmd->getNamespaceDef()->name() && namespaceName.mid(ni+2)==tmd->name();
2578           //printf("notInNS=%d sameNS=%d\n",notInNS,sameNS);
2579           if (tmd && tmd->isStrong() && // C++11 enum class
2580               (notInNS || sameNS) &&
2581               namespaceName.length()>0  // enum is part of namespace so this should not be empty
2582              )
2583           {
2584             md=mmd;
2585             fd=mmd->getFileDef();
2586             gd=mmd->getGroupDef();
2587             if (gd && gd->isLinkable()) fd=0; else gd=0;
2588             //printf("Found scoped enum %s fd=%p gd=%p\n",
2589             //    qPrint(mmd->name()),fd,gd);
2590             return TRUE;
2591           }
2592         }
2593       }
2594       if (scopeOffset==0)
2595       {
2596         scopeOffset=-1;
2597       }
2598       else if ((scopeOffset=scopeName.findRev("::",scopeOffset-1))==-1)
2599       {
2600         scopeOffset=0;
2601       }
2602     } while (scopeOffset>=0);
2603 
2604     //else // no scope => global function
2605     {
2606       std::vector<const MemberDef *> members;
2607       // search for matches with strict static checking
2608       findMembersWithSpecificName(mn,args,TRUE,currentFile,checkCV,members);
2609       if (members.empty()) // nothing found
2610       {
2611         // search again without strict static checking
2612         findMembersWithSpecificName(mn,args,FALSE,currentFile,checkCV,members);
2613       }
2614       //printf("found %d members\n",members.count());
2615       if (members.size()!=1 && args=="()")
2616       {
2617         // no exact match found, but if args="()" an arbitrary
2618         // member will do
2619         //MemberNameIterator mni(*mn);
2620         //for (mni.toLast();(md=mni.current());--mni)
2621         for (auto it = mn->rbegin(); it!=mn->rend(); ++it)
2622         {
2623           const auto &mmd_p = *it;
2624           const MemberDef *mmd = mmd_p.get();
2625           //printf("Found member '%s'\n",qPrint(mmd->name()));
2626           //printf("member is linkable mmd->name()='%s'\n",qPrint(mmd->name()));
2627           fd=mmd->getFileDef();
2628           gd=mmd->getGroupDef();
2629           const MemberDef *tmd = mmd->getEnumScope();
2630           if (
2631               (gd && gd->isLinkable()) || (fd && fd->isLinkable()) ||
2632               (tmd && tmd->isStrong())
2633              )
2634           {
2635             members.push_back(mmd);
2636           }
2637         }
2638       }
2639       //printf("found %d candidate members\n",members.count());
2640       if (!members.empty()) // at least one match
2641       {
2642         if (currentFile)
2643         {
2644           //printf("multiple results; pick one from file:%s\n",qPrint( currentFile->name()));
2645           for (const auto &rmd : members)
2646           {
2647             if (rmd->getFileDef() && rmd->getFileDef()->name() == currentFile->name())
2648             {
2649               md = rmd;
2650               break; // found match in the current file
2651             }
2652           }
2653           if (!md) // member not in the current file
2654           {
2655             md=members.back();
2656           }
2657         }
2658         else
2659         {
2660           md=members.back();
2661         }
2662       }
2663       if (md && (md->getEnumScope()==0 || !md->getEnumScope()->isStrong()))
2664            // found a matching global member, that is not a scoped enum value (or uniquely matches)
2665       {
2666         fd=md->getFileDef();
2667         gd=md->getGroupDef();
2668         //printf("fd=%p gd=%p gd->isLinkable()=%d\n",fd,gd,gd->isLinkable());
2669         if (gd && gd->isLinkable()) fd=0; else gd=0;
2670         return TRUE;
2671       }
2672     }
2673   }
2674 
2675   // no nothing found
2676   return FALSE;
2677 }
2678 
2679 /*!
2680  * Searches for a scope definition given its name as a string via parameter
2681  * `scope`.
2682  *
2683  * The parameter `docScope` is a string representing the name of the scope in
2684  * which the `scope` string was found.
2685  *
2686  * The function returns TRUE if the scope is known and documented or
2687  * FALSE if it is not.
2688  * If TRUE is returned exactly one of the parameter `cd`, `nd`
2689  * will be non-zero:
2690  *   - if `cd` is non zero, the scope was a class pointed to by cd.
2691  *   - if `nd` is non zero, the scope was a namespace pointed to by nd.
2692  */
getScopeDefs(const QCString & docScope,const QCString & scope,ClassDef * & cd,NamespaceDef * & nd)2693 static bool getScopeDefs(const QCString &docScope,const QCString &scope,
2694     ClassDef *&cd, NamespaceDef *&nd)
2695 {
2696   cd=0;nd=0;
2697 
2698   QCString scopeName=scope;
2699   //printf("getScopeDefs: docScope='%s' scope='%s'\n",docScope,scope);
2700   if (scopeName.isEmpty()) return FALSE;
2701 
2702   bool explicitGlobalScope=FALSE;
2703   if (scopeName.at(0)==':' && scopeName.at(1)==':')
2704   {
2705     scopeName=scopeName.right(scopeName.length()-2);
2706     explicitGlobalScope=TRUE;
2707   }
2708   if (scopeName.isEmpty())
2709   {
2710     return FALSE;
2711   }
2712 
2713   QCString docScopeName=docScope;
2714   int scopeOffset=explicitGlobalScope ? 0 : docScopeName.length();
2715 
2716   do // for each possible docScope (from largest to and including empty)
2717   {
2718     QCString fullName=scopeName;
2719     if (scopeOffset>0) fullName.prepend(docScopeName.left(scopeOffset)+"::");
2720 
2721     if (((cd=getClass(fullName)) ||         // normal class
2722          (cd=getClass(fullName+"-p")) //||    // ObjC protocol
2723          //(cd=getClass(fullName+"-g"))       // C# generic
2724         ) && cd->isLinkable())
2725     {
2726       return TRUE; // class link written => quit
2727     }
2728     else if ((nd=Doxygen::namespaceLinkedMap->find(fullName)) && nd->isLinkable())
2729     {
2730       return TRUE; // namespace link written => quit
2731     }
2732     if (scopeOffset==0)
2733     {
2734       scopeOffset=-1;
2735     }
2736     else if ((scopeOffset=docScopeName.findRev("::",scopeOffset-1))==-1)
2737     {
2738       scopeOffset=0;
2739     }
2740   } while (scopeOffset>=0);
2741 
2742   return FALSE;
2743 }
2744 
isLowerCase(QCString & s)2745 static bool isLowerCase(QCString &s)
2746 {
2747   if (s.isEmpty()) return true;
2748   uchar *p=(uchar*)s.data();
2749   int c;
2750   while ((c=*p++)) if (!islower(c)) return false;
2751   return true;
2752 }
2753 
2754 /*! Returns an object to reference to given its name and context
2755  *  @post return value TRUE implies *resContext!=0 or *resMember!=0
2756  */
resolveRef(const QCString & scName,const QCString & name,bool inSeeBlock,const Definition ** resContext,const MemberDef ** resMember,bool lookForSpecialization,const FileDef * currentFile,bool checkScope)2757 bool resolveRef(/* in */  const QCString &scName,
2758     /* in */  const QCString &name,
2759     /* in */  bool inSeeBlock,
2760     /* out */ const Definition **resContext,
2761     /* out */ const MemberDef  **resMember,
2762     bool lookForSpecialization,
2763     const FileDef *currentFile,
2764     bool checkScope
2765     )
2766 {
2767   //printf("resolveRef(scope=%s,name=%s,inSeeBlock=%d)\n",qPrint(scName),qPrint(name),inSeeBlock);
2768   QCString tsName = name;
2769   //bool memberScopeFirst = tsName.find('#')!=-1;
2770   QCString fullName = substitute(tsName,"#","::");
2771   if (fullName.find("anonymous_namespace{")==-1)
2772   {
2773     fullName = removeRedundantWhiteSpace(substitute(fullName,".","::",3));
2774   }
2775   else
2776   {
2777     fullName = removeRedundantWhiteSpace(fullName);
2778   }
2779 
2780   int bracePos=findParameterList(fullName);
2781   int endNamePos=bracePos!=-1 ? bracePos : fullName.length();
2782   int scopePos=fullName.findRev("::",endNamePos);
2783   bool explicitScope = fullName.left(2)=="::" &&   // ::scope or #scope
2784                        (scopePos>2 ||              // ::N::A
2785                         tsName.left(2)=="::" ||    // ::foo in local scope
2786                         scName==0                  // #foo  in global scope
2787                        );
2788 
2789   // default result values
2790   *resContext=0;
2791   *resMember=0;
2792 
2793   if (bracePos==-1) // simple name
2794   {
2795     ClassDef *cd=0;
2796     NamespaceDef *nd=0;
2797 
2798     // the following if() was commented out for releases in the range
2799     // 1.5.2 to 1.6.1, but has been restored as a result of bug report 594787.
2800     if (!inSeeBlock && scopePos==-1 && isLowerCase(tsName))
2801     { // link to lower case only name => do not try to autolink
2802       return FALSE;
2803     }
2804 
2805     //printf("scName=%s fullName=%s\n",scName,qPrint(fullName));
2806 
2807     // check if this is a class or namespace reference
2808     if (scName!=fullName && getScopeDefs(scName,fullName,cd,nd))
2809     {
2810       if (cd) // scope matches that of a class
2811       {
2812         *resContext = cd;
2813       }
2814       else // scope matches that of a namespace
2815       {
2816         ASSERT(nd!=0);
2817         *resContext = nd;
2818       }
2819       return TRUE;
2820     }
2821     else if (scName==fullName || (!inSeeBlock && scopePos==-1))
2822       // nothing to link => output plain text
2823     {
2824       //printf("found scName=%s fullName=%s scName==fullName=%d "
2825       //    "inSeeBlock=%d scopePos=%d!\n",
2826       //    scName,qPrint(fullName),scName==fullName,inSeeBlock,scopePos);
2827       return FALSE;
2828     }
2829     // continue search...
2830   }
2831 
2832   // extract userscope+name
2833   QCString nameStr=fullName.left(endNamePos);
2834   if (explicitScope) nameStr=nameStr.mid(2);
2835 
2836   // extract arguments
2837   QCString argsStr;
2838   if (bracePos!=-1) argsStr=fullName.right(fullName.length()-bracePos);
2839 
2840   // strip template specifier
2841   // TODO: match against the correct partial template instantiation
2842   int templPos=nameStr.find('<');
2843   bool tryUnspecializedVersion = FALSE;
2844   if (templPos!=-1 && nameStr.find("operator")==-1)
2845   {
2846     int endTemplPos=nameStr.findRev('>');
2847     if (endTemplPos!=-1)
2848     {
2849       if (!lookForSpecialization)
2850       {
2851         nameStr=nameStr.left(templPos)+nameStr.right(nameStr.length()-endTemplPos-1);
2852       }
2853       else
2854       {
2855         tryUnspecializedVersion = TRUE;
2856       }
2857     }
2858   }
2859 
2860   QCString scopeStr=scName;
2861 
2862   const MemberDef    *md = 0;
2863   const ClassDef     *cd = 0;
2864   const FileDef      *fd = 0;
2865   const NamespaceDef *nd = 0;
2866   const GroupDef     *gd = 0;
2867   const ConceptDef   *cnd = 0;
2868 
2869   // check if nameStr is a member or global.
2870   //printf("getDefs(scope=%s,name=%s,args=%s checkScope=%d)\n",
2871   //    qPrint(scopeStr), qPrint(nameStr), qPrint(argsStr),checkScope);
2872   if (getDefs(scopeStr,nameStr,argsStr,
2873         md,cd,fd,nd,gd,
2874         //scopePos==0 && !memberScopeFirst, // forceEmptyScope
2875         explicitScope, // replaces prev line due to bug 600829
2876         currentFile,
2877         TRUE                              // checkCV
2878         )
2879      )
2880   {
2881     //printf("after getDefs checkScope=%d nameStr=%s cd=%p nd=%p\n",checkScope,qPrint(nameStr),cd,nd);
2882     if (checkScope && md && md->getOuterScope()==Doxygen::globalScope &&
2883         !md->isStrongEnumValue() &&
2884         (!scopeStr.isEmpty() || nameStr.find("::")>0))
2885     {
2886       // we did find a member, but it is a global one while we were explicitly
2887       // looking for a scoped variable. See bug 616387 for an example why this check is needed.
2888       // note we do need to support autolinking to "::symbol" hence the >0
2889       //printf("not global member!\n");
2890       *resContext=0;
2891       *resMember=0;
2892       return FALSE;
2893     }
2894     //printf("after getDefs md=%p cd=%p fd=%p nd=%p gd=%p\n",md,cd,fd,nd,gd);
2895     if      (md) { *resMember=md; *resContext=md; }
2896     else if (cd) *resContext=cd;
2897     else if (nd) *resContext=nd;
2898     else if (fd) *resContext=fd;
2899     else if (gd) *resContext=gd;
2900     else         { *resContext=0; *resMember=0; return FALSE; }
2901     //printf("member=%s (md=%p) anchor=%s linkable()=%d context=%s\n",
2902     //    qPrint(md->name()), md, qPrint(md->anchor()), md->isLinkable(), qPrint((*resContext)->name()));
2903     return TRUE;
2904   }
2905   else if (inSeeBlock && !nameStr.isEmpty() && (gd=Doxygen::groupLinkedMap->find(nameStr)))
2906   { // group link
2907     *resContext=gd;
2908     return TRUE;
2909   }
2910   else if ((cnd=Doxygen::conceptLinkedMap->find(nameStr)))
2911   {
2912     *resContext=cnd;
2913     return TRUE;
2914   }
2915   else if (tsName.find('.')!=-1) // maybe a link to a file
2916   {
2917     bool ambig;
2918     fd=findFileDef(Doxygen::inputNameLinkedMap,tsName,ambig);
2919     if (fd && !ambig)
2920     {
2921       *resContext=fd;
2922       return TRUE;
2923     }
2924   }
2925 
2926   if (tryUnspecializedVersion)
2927   {
2928     return resolveRef(scName,name,inSeeBlock,resContext,resMember,FALSE,0,checkScope);
2929   }
2930   if (bracePos!=-1) // Try without parameters as well, could be a constructor invocation
2931   {
2932     *resContext=getClass(fullName.left(bracePos));
2933     if (*resContext)
2934     {
2935       return TRUE;
2936     }
2937   }
2938   //printf("resolveRef: %s not found!\n",name);
2939 
2940   return FALSE;
2941 }
2942 
linkToText(SrcLangExt lang,const QCString & link,bool isFileName)2943 QCString linkToText(SrcLangExt lang,const QCString &link,bool isFileName)
2944 {
2945   //static bool optimizeOutputJava = Config_getBool(OPTIMIZE_OUTPUT_JAVA);
2946   QCString result=link;
2947   if (!result.isEmpty())
2948   {
2949     // replace # by ::
2950     result=substitute(result,"#","::");
2951     // replace . by ::
2952     if (!isFileName && result.find('<')==-1) result=substitute(result,".","::",3);
2953     // strip leading :: prefix if present
2954     if (result.at(0)==':' && result.at(1)==':')
2955     {
2956       result=result.right(result.length()-2);
2957     }
2958     QCString sep = getLanguageSpecificSeparator(lang);
2959     if (sep!="::")
2960     {
2961       result=substitute(result,"::",sep);
2962     }
2963   }
2964   return result;
2965 }
2966 
2967 #if 0
2968 /*
2969  * generate a reference to a class, namespace or member.
2970  * 'scName' is the name of the scope that contains the documentation
2971  * string that is returned.
2972  * 'name' is the name that we want to link to.
2973  * 'name' may have the following formats:
2974  *    1) "ScopeName"
2975  *    2) "memberName()"    one of the (overloaded) function or define
2976  *                         with name memberName.
2977  *    3) "memberName(...)" a specific (overloaded) function or define
2978  *                         with name memberName
2979  *    4) "::name           a global variable or define
2980  *    4) "\#memberName     member variable, global variable or define
2981  *    5) ("ScopeName::")+"memberName()"
2982  *    6) ("ScopeName::")+"memberName(...)"
2983  *    7) ("ScopeName::")+"memberName"
2984  * instead of :: the \# symbol may also be used.
2985  */
2986 
2987 bool generateRef(OutputDocInterface &od,const char *scName,
2988     const char *name,bool inSeeBlock,const char *rt)
2989 {
2990   //printf("generateRef(scName=%s,name=%s,inSee=%d,rt=%s)\n",scName,name,inSeeBlock,rt);
2991 
2992   Definition *compound;
2993   MemberDef *md;
2994 
2995   // create default link text
2996   QCString linkText = linkToText(rt,FALSE);
2997 
2998   if (resolveRef(scName,name,inSeeBlock,&compound,&md))
2999   {
3000     if (md && md->isLinkable()) // link to member
3001     {
3002       od.writeObjectLink(md->getReference(),
3003           md->getOutputFileBase(),
3004           md->anchor(),linkText);
3005       // generate the page reference (for LaTeX)
3006       if (!md->isReference())
3007       {
3008         writePageRef(od,md->getOutputFileBase(),md->anchor());
3009       }
3010       return TRUE;
3011     }
3012     else if (compound && compound->isLinkable()) // link to compound
3013     {
3014       if (rt==0 && compound->definitionType()==Definition::TypeGroup)
3015       {
3016         linkText=((GroupDef *)compound)->groupTitle();
3017       }
3018       if (compound && compound->definitionType()==Definition::TypeFile)
3019       {
3020         linkText=linkToText(rt,TRUE);
3021       }
3022       od.writeObjectLink(compound->getReference(),
3023           compound->getOutputFileBase(),
3024           0,linkText);
3025       if (!compound->isReference())
3026       {
3027         writePageRef(od,compound->getOutputFileBase(),0);
3028       }
3029       return TRUE;
3030     }
3031   }
3032   od.docify(linkText);
3033   return FALSE;
3034 }
3035 #endif
3036 
resolveLink(const QCString & scName,const QCString & lr,bool,const Definition ** resContext,QCString & resAnchor)3037 bool resolveLink(/* in */ const QCString &scName,
3038     /* in */ const QCString &lr,
3039     /* in */ bool /*inSeeBlock*/,
3040     /* out */ const Definition **resContext,
3041     /* out */ QCString &resAnchor
3042     )
3043 {
3044   *resContext=0;
3045 
3046   QCString linkRef=lr;
3047   QCString linkRefWithoutTemplates = stripTemplateSpecifiersFromScope(linkRef,FALSE);
3048   //printf("ResolveLink linkRef=%s\n",qPrint(lr));
3049   const FileDef  *fd;
3050   const GroupDef *gd;
3051   const PageDef  *pd;
3052   const ClassDef *cd;
3053   const DirDef   *dir;
3054   const ConceptDef *cnd;
3055   const NamespaceDef *nd;
3056   const SectionInfo *si=0;
3057   bool ambig;
3058   if (linkRef.isEmpty()) // no reference name!
3059   {
3060     return FALSE;
3061   }
3062   else if ((pd=Doxygen::pageLinkedMap->find(linkRef))) // link to a page
3063   {
3064     gd = pd->getGroupDef();
3065     if (gd)
3066     {
3067       if (!pd->name().isEmpty()) si=SectionManager::instance().find(pd->name());
3068       *resContext=gd;
3069       if (si) resAnchor = si->label();
3070     }
3071     else
3072     {
3073       *resContext=pd;
3074     }
3075     return TRUE;
3076   }
3077   else if ((si=SectionManager::instance().find(linkRef)))
3078   {
3079     *resContext=si->definition();
3080     resAnchor = si->label();
3081     return TRUE;
3082   }
3083   else if ((pd=Doxygen::exampleLinkedMap->find(linkRef))) // link to an example
3084   {
3085     *resContext=pd;
3086     return TRUE;
3087   }
3088   else if ((gd=Doxygen::groupLinkedMap->find(linkRef))) // link to a group
3089   {
3090     *resContext=gd;
3091     return TRUE;
3092   }
3093   else if ((fd=findFileDef(Doxygen::inputNameLinkedMap,linkRef,ambig)) // file link
3094       && fd->isLinkable())
3095   {
3096     *resContext=fd;
3097     return TRUE;
3098   }
3099   else if ((cd=getClass(linkRef))) // class link
3100   {
3101     *resContext=cd;
3102     resAnchor=cd->anchor();
3103     return TRUE;
3104   }
3105   else if ((cd=getClass(linkRefWithoutTemplates))) // C#/Java generic class link
3106   {
3107     *resContext=cd;
3108     resAnchor=cd->anchor();
3109     return TRUE;
3110   }
3111   else if ((cd=getClass(linkRef+"-p"))) // Obj-C protocol link
3112   {
3113     *resContext=cd;
3114     resAnchor=cd->anchor();
3115     return TRUE;
3116   }
3117   else if ((cnd=getConcept(linkRef))) // C++20 concept definition
3118   {
3119     *resContext=cnd;
3120     resAnchor=cnd->anchor();
3121     return TRUE;
3122   }
3123   else if ((nd=Doxygen::namespaceLinkedMap->find(linkRef)))
3124   {
3125     *resContext=nd;
3126     return TRUE;
3127   }
3128   else if ((dir=Doxygen::dirLinkedMap->find(FileInfo(linkRef.str()).absFilePath()+"/"))
3129       && dir->isLinkable()) // TODO: make this location independent like filedefs
3130   {
3131     *resContext=dir;
3132     return TRUE;
3133   }
3134   else // probably a member reference
3135   {
3136     const MemberDef *md = 0;
3137     bool res = resolveRef(scName,lr,TRUE,resContext,&md);
3138     if (md) resAnchor=md->anchor();
3139     return res;
3140   }
3141 }
3142 
3143 
3144 //----------------------------------------------------------------------
3145 // General function that generates the HTML code for a reference to some
3146 // file, class or member from text 'lr' within the context of class 'clName'.
3147 // This link has the text 'lt' (if not 0), otherwise 'lr' is used as a
3148 // basis for the link's text.
3149 // returns TRUE if a link could be generated.
3150 
generateLink(OutputDocInterface & od,const QCString & clName,const QCString & lr,bool inSeeBlock,const QCString & lt)3151 bool generateLink(OutputDocInterface &od,const QCString &clName,
3152     const QCString &lr,bool inSeeBlock,const QCString &lt)
3153 {
3154   //printf("generateLink(clName=%s,lr=%s,lr=%s)\n",clName,lr,lt);
3155   const Definition *compound = 0;
3156   //PageDef *pageDef=0;
3157   QCString anchor,linkText=linkToText(SrcLangExt_Unknown,lt,FALSE);
3158   //printf("generateLink linkText=%s\n",qPrint(linkText));
3159   if (resolveLink(clName,lr,inSeeBlock,&compound,anchor))
3160   {
3161     if (compound) // link to compound
3162     {
3163       if (lt.isEmpty() && anchor.isEmpty() &&                      /* compound link */
3164           compound->definitionType()==Definition::TypeGroup /* is group */
3165          )
3166       {
3167         linkText=(toGroupDef(compound))->groupTitle(); // use group's title as link
3168       }
3169       else if (compound->definitionType()==Definition::TypeFile)
3170       {
3171         linkText=linkToText(compound->getLanguage(),lt,TRUE);
3172       }
3173       od.writeObjectLink(compound->getReference(),
3174           compound->getOutputFileBase(),anchor,linkText);
3175       if (!compound->isReference())
3176       {
3177         writePageRef(od,compound->getOutputFileBase(),anchor);
3178       }
3179     }
3180     else
3181     {
3182       err("%s:%d: Internal error: resolveLink successful but no compound found!",__FILE__,__LINE__);
3183     }
3184     return TRUE;
3185   }
3186   else // link could not be found
3187   {
3188     od.docify(linkText);
3189     return FALSE;
3190   }
3191 }
3192 
generateFileRef(OutputDocInterface & od,const QCString & name,const QCString & text)3193 void generateFileRef(OutputDocInterface &od,const QCString &name,const QCString &text)
3194 {
3195   //printf("generateFileRef(%s,%s)\n",name,text);
3196   QCString linkText = text.isEmpty() ? text : name;
3197   //FileInfo *fi;
3198   FileDef *fd;
3199   bool ambig;
3200   if ((fd=findFileDef(Doxygen::inputNameLinkedMap,name,ambig)) &&
3201       fd->isLinkable())
3202     // link to documented input file
3203     od.writeObjectLink(fd->getReference(),fd->getOutputFileBase(),QCString(),linkText);
3204   else
3205     od.docify(linkText);
3206 }
3207 
3208 //----------------------------------------------------------------------
3209 
3210 /** Cache element for the file name to FileDef mapping cache. */
3211 struct FindFileCacheElem
3212 {
FindFileCacheElemFindFileCacheElem3213   FindFileCacheElem(FileDef *fd,bool ambig) : fileDef(fd), isAmbig(ambig) {}
3214   FileDef *fileDef;
3215   bool isAmbig;
3216 };
3217 
3218 static Cache<std::string,FindFileCacheElem> g_findFileDefCache(5000);
3219 
3220 static std::mutex g_findFileDefMutex;
3221 
findFileDef(const FileNameLinkedMap * fnMap,const QCString & n,bool & ambig)3222 FileDef *findFileDef(const FileNameLinkedMap *fnMap,const QCString &n,bool &ambig)
3223 {
3224   ambig=FALSE;
3225   if (n.isEmpty()) return 0;
3226 
3227   std::lock_guard<std::mutex> lock(g_findFileDefMutex);
3228 
3229   const int maxAddrSize = 20;
3230   char addr[maxAddrSize];
3231   qsnprintf(addr,maxAddrSize,"%p:",(void*)fnMap);
3232   QCString key = addr;
3233   key+=n;
3234 
3235   FindFileCacheElem *cachedResult = g_findFileDefCache.find(key.str());
3236   //printf("key=%s cachedResult=%p\n",qPrint(key),cachedResult);
3237   if (cachedResult)
3238   {
3239     ambig = cachedResult->isAmbig;
3240     //printf("cached: fileDef=%p\n",cachedResult->fileDef);
3241     return cachedResult->fileDef;
3242   }
3243   else
3244   {
3245     cachedResult = g_findFileDefCache.insert(key.str(),FindFileCacheElem(0,FALSE));
3246   }
3247 
3248   QCString name=Dir::cleanDirPath(n.str());
3249   QCString path;
3250   int slashPos;
3251   const FileName *fn;
3252   if (name.isEmpty()) goto exit;
3253   slashPos=std::max(name.findRev('/'),name.findRev('\\'));
3254   if (slashPos!=-1)
3255   {
3256     path=name.left(slashPos+1);
3257     name=name.right(name.length()-slashPos-1);
3258   }
3259   if (name.isEmpty()) goto exit;
3260   if ((fn=fnMap->find(name)))
3261   {
3262     //printf("fn->size()=%zu\n",fn->size());
3263     if (fn->size()==1)
3264     {
3265       const std::unique_ptr<FileDef> &fd = fn->front();
3266       bool isSamePath = Portable::fileSystemIsCaseSensitive() ?
3267                  fd->getPath().right(path.length())==path :
3268                  fd->getPath().right(path.length()).lower()==path.lower();
3269       if (path.isEmpty() || isSamePath)
3270       {
3271         cachedResult->fileDef = fd.get();
3272         return fd.get();
3273       }
3274     }
3275     else // file name alone is ambiguous
3276     {
3277       int count=0;
3278       FileDef *lastMatch=0;
3279       QCString pathStripped = stripFromIncludePath(path);
3280       for (const auto &fd_p : *fn)
3281       {
3282         FileDef *fd = fd_p.get();
3283         QCString fdStripPath = stripFromIncludePath(fd->getPath());
3284         if (path.isEmpty() || fdStripPath.right(pathStripped.length())==pathStripped)
3285         {
3286           count++;
3287           lastMatch=fd;
3288         }
3289       }
3290 
3291       ambig=(count>1);
3292       cachedResult->isAmbig = ambig;
3293       cachedResult->fileDef = lastMatch;
3294       return lastMatch;
3295     }
3296   }
3297   else
3298   {
3299     //printf("not found!\n");
3300   }
3301 exit:
3302   //delete cachedResult;
3303   return 0;
3304 }
3305 
3306 //----------------------------------------------------------------------
3307 
showFileDefMatches(const FileNameLinkedMap * fnMap,const QCString & n)3308 QCString showFileDefMatches(const FileNameLinkedMap *fnMap,const QCString &n)
3309 {
3310   QCString result;
3311   QCString name=n;
3312   QCString path;
3313   int slashPos=std::max(name.findRev('/'),name.findRev('\\'));
3314   if (slashPos!=-1)
3315   {
3316     path=name.left(slashPos+1);
3317     name=name.right(name.length()-slashPos-1);
3318   }
3319   const FileName *fn;
3320   if ((fn=fnMap->find(name)))
3321   {
3322     for (const auto &fd : *fn)
3323     {
3324       if (path.isEmpty() || fd->getPath().right(path.length())==path)
3325       {
3326         result+="   "+fd->absFilePath()+"\n";
3327       }
3328     }
3329   }
3330   return result;
3331 }
3332 
3333 //----------------------------------------------------------------------
3334 
substituteKeywords(const QCString & s,const QCString & title,const QCString & projName,const QCString & projNum,const QCString & projBrief)3335 QCString substituteKeywords(const QCString &s,const QCString &title,
3336          const QCString &projName,const QCString &projNum,const QCString &projBrief)
3337 {
3338   QCString result = s;
3339   if (!title.isEmpty()) result = substitute(result,"$title",title);
3340   result = substitute(result,"$datetime",dateToString(TRUE));
3341   result = substitute(result,"$date",dateToString(FALSE));
3342   result = substitute(result,"$year",yearToString());
3343   result = substitute(result,"$doxygenversion",getDoxygenVersion());
3344   result = substitute(result,"$projectname",projName);
3345   result = substitute(result,"$projectnumber",projNum);
3346   result = substitute(result,"$projectbrief",projBrief);
3347   result = substitute(result,"$projectlogo",stripPath(Config_getString(PROJECT_LOGO)));
3348   return result;
3349 }
3350 
3351 //----------------------------------------------------------------------
3352 
3353 /*! Returns the character index within \a name of the first prefix
3354  *  in Config_getList(IGNORE_PREFIX) that matches \a name at the left hand side,
3355  *  or zero if no match was found
3356  */
getPrefixIndex(const QCString & name)3357 int getPrefixIndex(const QCString &name)
3358 {
3359   if (name.isEmpty()) return 0;
3360   const StringVector &sl = Config_getList(IGNORE_PREFIX);
3361   for (const auto &s : sl)
3362   {
3363     const char *ps=s.c_str();
3364     const char *pd=name.data();
3365     int i=0;
3366     while (*ps!=0 && *pd!=0 && *ps==*pd) ps++,pd++,i++;
3367     if (*ps==0 && *pd!=0)
3368     {
3369       return i;
3370     }
3371   }
3372   return 0;
3373 }
3374 
3375 //----------------------------------------------------------------------------
3376 
3377 //----------------------------------------------------------------------
3378 
3379 #if 0
3380 // copies the next UTF8 character from input stream into buffer ids
3381 // returns the size of the character in bytes (or 0 if it is invalid)
3382 // the character itself will be copied as a UTF-8 encoded string to ids.
3383 int getUtf8Char(const char *input,char ids[MAX_UTF8_CHAR_SIZE],CaseModifier modifier)
3384 {
3385   int inputLen=1;
3386   const unsigned char uc = (unsigned char)*input;
3387   bool validUTF8Char = false;
3388   if (uc <= 0xf7)
3389   {
3390     const char* pt = input+1;
3391     int l = 0;
3392     if ((uc&0x80)==0x00)
3393     {
3394       switch (modifier)
3395       {
3396         case CaseModifier::None:    ids[0]=*input;                break;
3397         case CaseModifier::ToUpper: ids[0]=(char)toupper(*input); break;
3398         case CaseModifier::ToLower: ids[0]=(char)tolower(*input); break;
3399       }
3400       l=1; // 0xxx.xxxx => normal single byte ascii character
3401     }
3402     else
3403     {
3404       ids[ 0 ] = *input;
3405       if ((uc&0xE0)==0xC0)
3406       {
3407         l=2; // 110x.xxxx: >=2 byte character
3408       }
3409       if ((uc&0xF0)==0xE0)
3410       {
3411         l=3; // 1110.xxxx: >=3 byte character
3412       }
3413       if ((uc&0xF8)==0xF0)
3414       {
3415         l=4; // 1111.0xxx: >=4 byte character
3416       }
3417     }
3418     validUTF8Char = l>0;
3419     for (int m=1; m<l && validUTF8Char; ++m)
3420     {
3421       unsigned char ct = (unsigned char)*pt;
3422       if (ct==0 || (ct&0xC0)!=0x80) // invalid unicode character
3423       {
3424         validUTF8Char=false;
3425       }
3426       else
3427       {
3428         ids[ m ] = *pt++;
3429       }
3430     }
3431     if (validUTF8Char) // got a valid unicode character
3432     {
3433       ids[ l ] = 0;
3434       inputLen=l;
3435     }
3436   }
3437   return inputLen;
3438 }
3439 #endif
3440 
3441 // note that this function is not reentrant due to the use of static growBuf!
escapeCharsInString(const QCString & name,bool allowDots,bool allowUnderscore)3442 QCString escapeCharsInString(const QCString &name,bool allowDots,bool allowUnderscore)
3443 {
3444   if (name.isEmpty()) return name;
3445   bool caseSenseNames = Config_getBool(CASE_SENSE_NAMES);
3446   bool allowUnicodeNames = Config_getBool(ALLOW_UNICODE_NAMES);
3447   GrowBuf growBuf;
3448   signed char c;
3449   const char *p=name.data();
3450   while ((c=*p++)!=0)
3451   {
3452     switch(c)
3453     {
3454       case '_': if (allowUnderscore) growBuf.addChar('_'); else growBuf.addStr("__"); break;
3455       case '-': growBuf.addChar('-');  break;
3456       case ':': growBuf.addStr("_1"); break;
3457       case '/': growBuf.addStr("_2"); break;
3458       case '<': growBuf.addStr("_3"); break;
3459       case '>': growBuf.addStr("_4"); break;
3460       case '*': growBuf.addStr("_5"); break;
3461       case '&': growBuf.addStr("_6"); break;
3462       case '|': growBuf.addStr("_7"); break;
3463       case '.': if (allowDots) growBuf.addChar('.'); else growBuf.addStr("_8"); break;
3464       case '!': growBuf.addStr("_9"); break;
3465       case ',': growBuf.addStr("_00"); break;
3466       case ' ': growBuf.addStr("_01"); break;
3467       case '{': growBuf.addStr("_02"); break;
3468       case '}': growBuf.addStr("_03"); break;
3469       case '?': growBuf.addStr("_04"); break;
3470       case '^': growBuf.addStr("_05"); break;
3471       case '%': growBuf.addStr("_06"); break;
3472       case '(': growBuf.addStr("_07"); break;
3473       case ')': growBuf.addStr("_08"); break;
3474       case '+': growBuf.addStr("_09"); break;
3475       case '=': growBuf.addStr("_0a"); break;
3476       case '$': growBuf.addStr("_0b"); break;
3477       case '\\': growBuf.addStr("_0c"); break;
3478       case '@': growBuf.addStr("_0d"); break;
3479       case ']': growBuf.addStr("_0e"); break;
3480       case '[': growBuf.addStr("_0f"); break;
3481       case '#': growBuf.addStr("_0g"); break;
3482       default:
3483                 if (c<0)
3484                 {
3485                   bool doEscape = true;
3486                   if (allowUnicodeNames)
3487                   {
3488                     int charLen = getUTF8CharNumBytes(c);
3489                     if (charLen>0)
3490                     {
3491                       growBuf.addStr(p-1,charLen);
3492                       p+=charLen;
3493                       doEscape = false;
3494                     }
3495                   }
3496                   if (doEscape) // not a valid unicode char or escaping needed
3497                   {
3498                     char ids[5];
3499                     unsigned char id = (unsigned char)c;
3500                     ids[0]='_';
3501                     ids[1]='x';
3502                     ids[2]=hex[id>>4];
3503                     ids[3]=hex[id&0xF];
3504                     ids[4]=0;
3505                     growBuf.addStr(ids);
3506                   }
3507                 }
3508                 else if (caseSenseNames || !isupper(c))
3509                 {
3510                   growBuf.addChar(c);
3511                 }
3512                 else
3513                 {
3514                   growBuf.addChar('_');
3515                   growBuf.addChar((char)tolower(c));
3516                 }
3517                 break;
3518     }
3519   }
3520   growBuf.addChar(0);
3521   return growBuf.get();
3522 }
3523 
unescapeCharsInString(const QCString & s)3524 QCString unescapeCharsInString(const QCString &s)
3525 {
3526   if (s.isEmpty()) return s;
3527   bool caseSenseNames = Config_getBool(CASE_SENSE_NAMES);
3528   QCString result;
3529   const char *p = s.data();
3530   if (p)
3531   {
3532     char c;
3533     while ((c=*p++))
3534     {
3535       if (c=='_') // 2 or 3 character escape
3536       {
3537         switch (*p)
3538         {
3539           case '_': result+=c;   p++; break; // __ -> '_'
3540           case '1': result+=':'; p++; break; // _1 -> ':'
3541           case '2': result+='/'; p++; break; // _2 -> '/'
3542           case '3': result+='<'; p++; break; // _3 -> '<'
3543           case '4': result+='>'; p++; break; // _4 -> '>'
3544           case '5': result+='*'; p++; break; // _5 -> '*'
3545           case '6': result+='&'; p++; break; // _6 -> '&'
3546           case '7': result+='|'; p++; break; // _7 -> '|'
3547           case '8': result+='.'; p++; break; // _8 -> '.'
3548           case '9': result+='!'; p++; break; // _9 -> '!'
3549           case '0': // 3 character escape
3550              switch (*(p+1))
3551              {
3552                case '0': result+=','; p+=2; break; // _00 -> ','
3553                case '1': result+=' '; p+=2; break; // _01 -> ' '
3554                case '2': result+='{'; p+=2; break; // _02 -> '{'
3555                case '3': result+='}'; p+=2; break; // _03 -> '}'
3556                case '4': result+='?'; p+=2; break; // _04 -> '?'
3557                case '5': result+='^'; p+=2; break; // _05 -> '^'
3558                case '6': result+='%'; p+=2; break; // _06 -> '%'
3559                case '7': result+='('; p+=2; break; // _07 -> '('
3560                case '8': result+=')'; p+=2; break; // _08 -> ')'
3561                case '9': result+='+'; p+=2; break; // _09 -> '+'
3562                case 'a': result+='='; p+=2; break; // _0a -> '='
3563                case 'b': result+='$'; p+=2; break; // _0b -> '$'
3564                case 'c': result+='\\'; p+=2; break;// _0c -> '\'
3565                case 'd': result+='@'; p+=2; break; // _0d -> '@'
3566                case 'e': result+=']'; p+=2; break; // _0e -> ']'
3567                case 'f': result+='['; p+=2; break; // _0f -> '['
3568                case 'g': result+='#'; p+=2; break; // _0g -> '#'
3569                default: // unknown escape, just pass underscore character as-is
3570                  result+=c;
3571                  break;
3572              }
3573              break;
3574           default:
3575             if (!caseSenseNames && c>='a' && c<='z') // lower to upper case escape, _a -> 'A'
3576             {
3577               result+=(char)toupper(*p);
3578               p++;
3579             }
3580             else // unknown escape, pass underscore character as-is
3581             {
3582               result+=c;
3583             }
3584             break;
3585         }
3586       }
3587       else // normal character; pass as is
3588       {
3589         result+=c;
3590       }
3591     }
3592   }
3593   return result;
3594 }
3595 
3596 static std::unordered_map<std::string,int> g_usedNames;
3597 static std::mutex g_usedNamesMutex;
3598 static int g_usedNamesCount=1;
3599 
3600 /*! This function determines the file name on disk of an item
3601  *  given its name, which could be a class name with template
3602  *  arguments, so special characters need to be escaped.
3603  */
convertNameToFile(const QCString & name,bool allowDots,bool allowUnderscore)3604 QCString convertNameToFile(const QCString &name,bool allowDots,bool allowUnderscore)
3605 {
3606   if (name.isEmpty()) return name;
3607   static bool shortNames = Config_getBool(SHORT_NAMES);
3608   static bool createSubdirs = Config_getBool(CREATE_SUBDIRS);
3609   QCString result;
3610   if (shortNames) // use short names only
3611   {
3612     std::lock_guard<std::mutex> lock(g_usedNamesMutex);
3613     auto kv = g_usedNames.find(name.str());
3614     uint num=0;
3615     if (kv!=g_usedNames.end())
3616     {
3617       num = kv->second;
3618     }
3619     else
3620     {
3621       num = g_usedNamesCount;
3622       g_usedNames.insert(std::make_pair(name.str(),g_usedNamesCount++));
3623     }
3624     result.sprintf("a%05d",num);
3625   }
3626   else // long names
3627   {
3628     result=escapeCharsInString(name,allowDots,allowUnderscore);
3629     int resultLen = result.length();
3630     if (resultLen>=128) // prevent names that cannot be created!
3631     {
3632       // third algorithm based on MD5 hash
3633       uchar md5_sig[16];
3634       char sigStr[33];
3635       MD5Buffer((const unsigned char *)result.data(),resultLen,md5_sig);
3636       MD5SigToString(md5_sig,sigStr);
3637       result=result.left(128-32)+sigStr;
3638     }
3639   }
3640   if (createSubdirs)
3641   {
3642     int l1Dir=0,l2Dir=0;
3643 
3644     // compute md5 hash to determine sub directory to use
3645     uchar md5_sig[16];
3646     MD5Buffer((const unsigned char *)result.data(),result.length(),md5_sig);
3647     l1Dir = md5_sig[14]&0xf;
3648     l2Dir = md5_sig[15];
3649 
3650     result.prepend(QCString().sprintf("d%x/d%02x/",l1Dir,l2Dir));
3651   }
3652   //printf("*** convertNameToFile(%s)->%s\n",qPrint(name),qPrint(result));
3653   return result;
3654 }
3655 
relativePathToRoot(const QCString & name)3656 QCString relativePathToRoot(const QCString &name)
3657 {
3658   QCString result;
3659   if (Config_getBool(CREATE_SUBDIRS))
3660   {
3661     if (name.isEmpty())
3662     {
3663       return REL_PATH_TO_ROOT;
3664     }
3665     else
3666     {
3667       int i = name.findRev('/');
3668       if (i!=-1)
3669       {
3670         result=REL_PATH_TO_ROOT;
3671       }
3672     }
3673   }
3674   return result;
3675 }
3676 
createSubDirs(const Dir & d)3677 void createSubDirs(const Dir &d)
3678 {
3679   if (Config_getBool(CREATE_SUBDIRS))
3680   {
3681     // create 4096 subdirectories
3682     int l1,l2;
3683     for (l1=0;l1<16;l1++)
3684     {
3685       QCString subdir;
3686       subdir.sprintf("d%x",l1);
3687       if (!d.exists(subdir.str()) && !d.mkdir(subdir.str()))
3688       {
3689         term("Failed to create output directory '%s'\n",qPrint(subdir));
3690       }
3691       for (l2=0;l2<256;l2++)
3692       {
3693         QCString subsubdir;
3694         subsubdir.sprintf("d%x/d%02x",l1,l2);
3695         if (!d.exists(subsubdir.str()) && !d.mkdir(subsubdir.str()))
3696         {
3697           term("Failed to create output directory '%s'\n",qPrint(subsubdir));
3698         }
3699       }
3700     }
3701   }
3702 }
3703 
clearSubDirs(const Dir & d)3704 void clearSubDirs(const Dir &d)
3705 {
3706   if (Config_getBool(CREATE_SUBDIRS))
3707   {
3708     // remove empty subdirectories
3709     for (int l1=0;l1<16;l1++)
3710     {
3711       QCString subdir;
3712       subdir.sprintf("d%x",l1);
3713       for (int l2=0;l2<256;l2++)
3714       {
3715         QCString subsubdir;
3716         subsubdir.sprintf("d%x/d%02x",l1,l2);
3717         if (d.exists(subsubdir.str()) && d.isEmpty(subsubdir.str()))
3718         {
3719           d.rmdir(subsubdir.str());
3720         }
3721       }
3722       if (d.exists(subdir.str()) && d.isEmpty(subdir.str()))
3723       {
3724         d.rmdir(subdir.str());
3725       }
3726     }
3727   }
3728 }
3729 
3730 /*! Input is a scopeName, output is the scopename split into a
3731  *  namespace part (as large as possible) and a classname part.
3732  */
extractNamespaceName(const QCString & scopeName,QCString & className,QCString & namespaceName,bool allowEmptyClass)3733 void extractNamespaceName(const QCString &scopeName,
3734     QCString &className,QCString &namespaceName,
3735     bool allowEmptyClass)
3736 {
3737   int i,p;
3738   QCString clName=scopeName;
3739   NamespaceDef *nd = 0;
3740   if (!clName.isEmpty() && (nd=getResolvedNamespace(clName)) && getClass(clName)==0)
3741   { // the whole name is a namespace (and not a class)
3742     namespaceName=nd->name();
3743     className.resize(0);
3744     goto done;
3745   }
3746   p=clName.length()-2;
3747   while (p>=0 && (i=clName.findRev("::",p))!=-1)
3748     // see if the first part is a namespace (and not a class)
3749   {
3750     //printf("Trying %s\n",qPrint(clName.left(i)));
3751     if (i>0 && (nd=getResolvedNamespace(clName.left(i))) && getClass(clName.left(i))==0)
3752     {
3753       //printf("found!\n");
3754       namespaceName=nd->name();
3755       className=clName.right(clName.length()-i-2);
3756       goto done;
3757     }
3758     p=i-2; // try a smaller piece of the scope
3759   }
3760   //printf("not found!\n");
3761 
3762   // not found, so we just have to guess.
3763   className=scopeName;
3764   namespaceName.resize(0);
3765 
3766 done:
3767   if (className.isEmpty() && !namespaceName.isEmpty() && !allowEmptyClass)
3768   {
3769     // class and namespace with the same name, correct to return the class.
3770     className=namespaceName;
3771     namespaceName.resize(0);
3772   }
3773   //printf("extractNamespace '%s' => '%s|%s'\n",qPrint(scopeName),
3774   //       qPrint(className),qPrint(namespaceName));
3775   if (/*className.right(2)=="-g" ||*/ className.right(2)=="-p")
3776   {
3777     className = className.left(className.length()-2);
3778   }
3779   return;
3780 }
3781 
insertTemplateSpecifierInScope(const QCString & scope,const QCString & templ)3782 QCString insertTemplateSpecifierInScope(const QCString &scope,const QCString &templ)
3783 {
3784   QCString result=scope;
3785   if (!templ.isEmpty() && scope.find('<')==-1)
3786   {
3787     int si,pi=0;
3788     ClassDef *cd=0;
3789     while (
3790         (si=scope.find("::",pi))!=-1 && !getClass(scope.left(si)+templ) &&
3791         ((cd=getClass(scope.left(si)))==0 || cd->templateArguments().empty())
3792         )
3793     {
3794       //printf("Tried '%s'\n",qPrint((scope.left(si)+templ)));
3795       pi=si+2;
3796     }
3797     if (si==-1) // not nested => append template specifier
3798     {
3799       result+=templ;
3800     }
3801     else // nested => insert template specifier before after first class name
3802     {
3803       result=scope.left(si) + templ + scope.right(scope.length()-si);
3804     }
3805   }
3806   //printf("insertTemplateSpecifierInScope('%s','%s')=%s\n",
3807   //    qPrint(scope),qPrint(templ),qPrint(result));
3808   return result;
3809 }
3810 
3811 
3812 /*! Strips the scope from a name. Examples: A::B will return A
3813  *  and A<T>::B<N::C<D> > will return A<T>.
3814  */
stripScope(const QCString & name)3815 QCString stripScope(const QCString &name)
3816 {
3817   QCString result = name;
3818   int l=result.length();
3819   int p;
3820   bool done = FALSE;
3821   bool skipBracket=FALSE; // if brackets do not match properly, ignore them altogether
3822   int count=0;
3823   int round=0;
3824 
3825   do
3826   {
3827     p=l-1; // start at the end of the string
3828     while (p>=0 && count>=0)
3829     {
3830       char c=result.at(p);
3831       switch (c)
3832       {
3833         case ':':
3834           // only exit in the case of ::
3835           //printf("stripScope(%s)=%s\n",name,qPrint(result.right(l-p-1)));
3836           if (p>0 && result.at(p-1)==':' && (count==0 || skipBracket))
3837           {
3838             return result.right(l-p-1);
3839           }
3840           p--;
3841           break;
3842         case '>':
3843           if (skipBracket) // we don't care about brackets
3844           {
3845             p--;
3846           }
3847           else // count open/close brackets
3848           {
3849             if (p>0 && result.at(p-1)=='>') // skip >> operator
3850             {
3851               p-=2;
3852               break;
3853             }
3854             count=1;
3855             //printf("pos < = %d\n",p);
3856             p--;
3857             bool foundMatch=false;
3858             while (p>=0 && !foundMatch)
3859             {
3860               c=result.at(p--);
3861               switch (c)
3862               {
3863                 case ')':
3864                   round++;
3865                   break;
3866                 case '(':
3867                   round--;
3868                   break;
3869                 case '>': // ignore > inside (...) to support e.g. (sizeof(T)>0) inside template parameters
3870                   if (round==0) count++;
3871                   break;
3872                 case '<':
3873                   if (round==0)
3874                   {
3875                     if (p>0)
3876                     {
3877                       if (result.at(p-1) == '<') // skip << operator
3878                       {
3879                         p--;
3880                         break;
3881                       }
3882                     }
3883                     count--;
3884                     foundMatch = count==0;
3885                   }
3886                   break;
3887                 default:
3888                   //printf("c=%c count=%d\n",c,count);
3889                   break;
3890               }
3891             }
3892           }
3893           //printf("pos > = %d\n",p+1);
3894           break;
3895         default:
3896           p--;
3897       }
3898     }
3899     done = count==0 || skipBracket; // reparse if brackets do not match
3900     skipBracket=TRUE;
3901   }
3902   while (!done); // if < > unbalanced repeat ignoring them
3903   //printf("stripScope(%s)=%s\n",name,name);
3904   return name;
3905 }
3906 
3907 /*! Converts a string to a HTML id string */
convertToId(const QCString & s)3908 QCString convertToId(const QCString &s)
3909 {
3910   if (s.isEmpty()) return s;
3911   GrowBuf growBuf;
3912   const char *p=s.data();
3913   char c;
3914   bool first=TRUE;
3915   while ((c=*p++))
3916   {
3917     char encChar[4];
3918     if ((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='-' || c==':' || c=='.')
3919     { // any permissive character except _
3920       if (first && c>='0' && c<='9') growBuf.addChar('a'); // don't start with a digit
3921       growBuf.addChar(c);
3922     }
3923     else
3924     {
3925       encChar[0]='_';
3926       encChar[1]=hex[((unsigned char)c)>>4];
3927       encChar[2]=hex[((unsigned char)c)&0xF];
3928       encChar[3]=0;
3929       growBuf.addStr(encChar);
3930     }
3931     first=FALSE;
3932   }
3933   growBuf.addChar(0);
3934   return growBuf.get();
3935 }
3936 
3937 /*! Some strings have been corrected but the requirement regarding the fact
3938  *  that an id cannot have a digit at the first position. To overcome problems
3939  *  with double labels we always place an "a" in front
3940  */
correctId(const QCString & s)3941 QCString correctId(const QCString &s)
3942 {
3943   if (s.isEmpty()) return s;
3944   return "a" + s;
3945 }
3946 
3947 /*! Converts a string to an XML-encoded string */
convertToXML(const QCString & s,bool keepEntities)3948 QCString convertToXML(const QCString &s, bool keepEntities)
3949 {
3950   if (s.isEmpty()) return s;
3951   GrowBuf growBuf;
3952   const char *p=s.data();
3953   char c;
3954   while ((c=*p++))
3955   {
3956     switch (c)
3957     {
3958       case '<':  growBuf.addStr("&lt;");   break;
3959       case '>':  growBuf.addStr("&gt;");   break;
3960       case '&':  if (keepEntities)
3961                  {
3962                    const char *e=p;
3963                    char ce;
3964                    while ((ce=*e++))
3965                    {
3966                      if (ce==';' || (!(isId(ce) || ce=='#'))) break;
3967                    }
3968                    if (ce==';') // found end of an entity
3969                    {
3970                      // copy entry verbatim
3971                      growBuf.addChar(c);
3972                      while (p<e) growBuf.addChar(*p++);
3973                    }
3974                    else
3975                    {
3976                      growBuf.addStr("&amp;");
3977                    }
3978                  }
3979                  else
3980                  {
3981                    growBuf.addStr("&amp;");
3982                  }
3983                  break;
3984       case '\'': growBuf.addStr("&apos;"); break;
3985       case '"':  growBuf.addStr("&quot;"); break;
3986       case  1: case  2: case  3: case  4: case  5: case  6: case  7: case  8:
3987       case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18:
3988       case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26:
3989       case 27: case 28: case 29: case 30: case 31:
3990         break; // skip invalid XML characters (see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char)
3991       default:   growBuf.addChar(c);       break;
3992     }
3993   }
3994   growBuf.addChar(0);
3995   return growBuf.get();
3996 }
3997 
3998 /*! Converts a string to an DocBook-encoded string */
convertToDocBook(const QCString & s,const bool retainNewline)3999 QCString convertToDocBook(const QCString &s, const bool retainNewline)
4000 {
4001   if (s.isEmpty()) return s;
4002   GrowBuf growBuf;
4003   const unsigned char *q;
4004   int cnt;
4005   const unsigned char *p=(const unsigned char *)s.data();
4006   char c;
4007   while ((c=*p++))
4008   {
4009     switch (c)
4010     {
4011       case '\n': if (retainNewline) growBuf.addStr("<literallayout>&#160;&#xa;</literallayout>"); growBuf.addChar(c);   break;
4012       case '<':  growBuf.addStr("&lt;");   break;
4013       case '>':  growBuf.addStr("&gt;");   break;
4014       case '&':  // possibility to have a special symbol
4015         q = p;
4016         cnt = 2; // we have to count & and ; as well
4017         while ((*q >= 'a' && *q <= 'z') || (*q >= 'A' && *q <= 'Z') || (*q >= '0' && *q <= '9'))
4018         {
4019           cnt++;
4020           q++;
4021         }
4022         if (*q == ';')
4023         {
4024            --p; // we need & as well
4025            DocSymbol::SymType res = HtmlEntityMapper::instance()->name2sym(QCString((char *)p).left(cnt));
4026            if (res == DocSymbol::Sym_Unknown)
4027            {
4028              p++;
4029              growBuf.addStr("&amp;");
4030            }
4031            else
4032            {
4033              growBuf.addStr(HtmlEntityMapper::instance()->docbook(res));
4034              q++;
4035              p = q;
4036            }
4037         }
4038         else
4039         {
4040           growBuf.addStr("&amp;");
4041         }
4042         break;
4043       case '\'': growBuf.addStr("&apos;"); break;
4044       case '"':  growBuf.addStr("&quot;"); break;
4045       case  1: case  2: case  3: case  4: case  5: case  6: case 7:  case  8:
4046       case 11: case 12: case 14: case 15: case 16: case 17: case 18:
4047       case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26:
4048       case 27: case 28: case 29: case 30: case 31:
4049         growBuf.addStr("&#x24");
4050         growBuf.addChar(hex[static_cast<uchar>(c)>>4]);
4051         growBuf.addChar(hex[static_cast<uchar>(c)&0xF]);
4052         growBuf.addChar(';');
4053         break;
4054       default:
4055         growBuf.addChar(c);
4056         break;
4057     }
4058   }
4059   growBuf.addChar(0);
4060   return growBuf.get();
4061 }
4062 
4063 /*! Converts a string to a HTML-encoded string */
convertToHtml(const QCString & s,bool keepEntities)4064 QCString convertToHtml(const QCString &s,bool keepEntities)
4065 {
4066   if (s.isEmpty()) return s;
4067   GrowBuf growBuf;
4068   const char *p=s.data();
4069   char c;
4070   while ((c=*p++))
4071   {
4072     switch (c)
4073     {
4074       case '<':  growBuf.addStr("&lt;");   break;
4075       case '>':  growBuf.addStr("&gt;");   break;
4076       case '&':  if (keepEntities)
4077                  {
4078                    const char *e=p;
4079                    char ce;
4080                    while ((ce=*e++))
4081                    {
4082                      if (ce==';' || (!(isId(ce) || ce=='#'))) break;
4083                    }
4084                    if (ce==';') // found end of an entity
4085                    {
4086                      // copy entry verbatim
4087                      growBuf.addChar(c);
4088                      while (p<e) growBuf.addChar(*p++);
4089                    }
4090                    else
4091                    {
4092                      growBuf.addStr("&amp;");
4093                    }
4094                  }
4095                  else
4096                  {
4097                    growBuf.addStr("&amp;");
4098                  }
4099                  break;
4100       case '\'': growBuf.addStr("&#39;");  break;
4101       case '"':  growBuf.addStr("&quot;"); break;
4102       default:
4103         {
4104           uchar uc = static_cast<uchar>(c);
4105           if (uc<32 && !isspace(c))
4106           {
4107             growBuf.addStr("&#x24");
4108             growBuf.addChar(hex[uc>>4]);
4109             growBuf.addChar(hex[uc&0xF]);
4110             growBuf.addChar(';');
4111           }
4112           else
4113           {
4114             growBuf.addChar(c);
4115           }
4116         }
4117         break;
4118     }
4119   }
4120   growBuf.addChar(0);
4121   return growBuf.get();
4122 }
4123 
convertToJSString(const QCString & s)4124 QCString convertToJSString(const QCString &s)
4125 {
4126   if (s.isEmpty()) return s;
4127   GrowBuf growBuf;
4128   const char *p=s.data();
4129   char c;
4130   while ((c=*p++))
4131   {
4132     switch (c)
4133     {
4134       case '"':  growBuf.addStr("\\\""); break;
4135       case '\\': growBuf.addStr("\\\\"); break;
4136       default:   growBuf.addChar(c);   break;
4137     }
4138   }
4139   growBuf.addChar(0);
4140   return convertCharEntitiesToUTF8(growBuf.get());
4141 }
4142 
convertToPSString(const QCString & s)4143 QCString convertToPSString(const QCString &s)
4144 {
4145   if (s.isEmpty()) return s;
4146   GrowBuf growBuf;
4147   const char *p=s.data();
4148   char c;
4149   while ((c=*p++))
4150   {
4151     switch (c)
4152     {
4153       case '(':  growBuf.addStr("\\("); break;
4154       case ')': growBuf.addStr("\\)"); break;
4155       default:   growBuf.addChar(c);   break;
4156     }
4157   }
4158   growBuf.addChar(0);
4159   return growBuf.get();
4160 }
4161 
convertToLaTeX(const QCString & s,bool insideTabbing,bool keepSpaces)4162 QCString convertToLaTeX(const QCString &s,bool insideTabbing,bool keepSpaces)
4163 {
4164   TextStream t;
4165   filterLatexString(t,s,insideTabbing,false,false,false,keepSpaces);
4166   return t.str();
4167 }
4168 
4169 
4170 
convertCharEntitiesToUTF8(const QCString & str)4171 QCString convertCharEntitiesToUTF8(const QCString &str)
4172 {
4173   if (str.isEmpty()) return QCString();
4174 
4175   std::string s = str.data();
4176   static const reg::Ex re(R"(&\a\w*;)");
4177   reg::Iterator it(s,re);
4178   reg::Iterator end;
4179 
4180   GrowBuf growBuf;
4181   size_t p,i=0,l;
4182   for (; it!=end ; ++it)
4183   {
4184     const auto &match = *it;
4185     p = match.position();
4186     l = match.length();
4187     if (p>i)
4188     {
4189       growBuf.addStr(s.substr(i,p-i));
4190     }
4191     QCString entity(match.str());
4192     DocSymbol::SymType symType = HtmlEntityMapper::instance()->name2sym(entity);
4193     const char *code=0;
4194     if (symType!=DocSymbol::Sym_Unknown && (code=HtmlEntityMapper::instance()->utf8(symType)))
4195     {
4196       growBuf.addStr(code);
4197     }
4198     else
4199     {
4200       growBuf.addStr(entity);
4201     }
4202     i=p+l;
4203   }
4204   growBuf.addStr(s.substr(i));
4205   growBuf.addChar(0);
4206   //printf("convertCharEntitiesToUTF8(%s)->%s\n",qPrint(s),growBuf.get());
4207   return growBuf.get();
4208 }
4209 
4210 /*! Returns the standard string that is generated when the \\overload
4211  * command is used.
4212  */
getOverloadDocs()4213 QCString getOverloadDocs()
4214 {
4215   return theTranslator->trOverloadText();
4216   //"This is an overloaded member function, "
4217   //       "provided for convenience. It differs from the above "
4218   //       "function only in what argument(s) it accepts.";
4219 }
4220 
addMembersToMemberGroup(MemberList * ml,MemberGroupList * pMemberGroups,const Definition * context)4221 void addMembersToMemberGroup(MemberList *ml,
4222     MemberGroupList *pMemberGroups,
4223     const Definition *context)
4224 {
4225   ASSERT(context!=0);
4226   //printf("addMemberToMemberGroup() context=%s\n",qPrint(context->name()));
4227   if (ml==0) return;
4228 
4229   struct MoveMemberInfo
4230   {
4231     MoveMemberInfo(const MemberDef *md,MemberGroup *mg,const RefItemVector &rv)
4232       : memberDef(md), memberGroup(mg), sli(rv) {}
4233     const MemberDef *memberDef;
4234     MemberGroup *memberGroup;
4235     RefItemVector sli;
4236   };
4237   std::vector<MoveMemberInfo> movedMembers;
4238 
4239   for (const auto &md : *ml)
4240   {
4241     if (md->isEnumerate()) // insert enum value of this enum into groups
4242     {
4243       for (const auto &fmd : md->enumFieldList())
4244       {
4245         int groupId=fmd->getMemberGroupId();
4246         if (groupId!=-1)
4247         {
4248           auto it = Doxygen::memberGroupInfoMap.find(groupId);
4249           if (it!=Doxygen::memberGroupInfoMap.end())
4250           {
4251             auto &info = it->second;
4252             auto mg_it = std::find_if(pMemberGroups->begin(),
4253                                       pMemberGroups->end(),
4254                                       [&groupId](const auto &g)
4255                                       { return g->groupId()==groupId; }
4256                                      );
4257             MemberGroup *mg_ptr = 0;
4258             if (mg_it==pMemberGroups->end())
4259             {
4260               auto mg = std::make_unique<MemberGroup>(
4261                         context,
4262                         groupId,
4263                         info->header,
4264                         info->doc,
4265                         info->docFile,
4266                         info->docLine,
4267                         ml->container());
4268               mg_ptr = mg.get();
4269               pMemberGroups->push_back(std::move(mg));
4270             }
4271             else
4272             {
4273               mg_ptr = (*mg_it).get();
4274             }
4275             mg_ptr->insertMember(fmd); // insert in member group
4276             MemberDefMutable *fmdm = toMemberDefMutable(fmd);
4277             if (fmdm)
4278             {
4279               fmdm->setMemberGroup(mg_ptr);
4280             }
4281           }
4282         }
4283       }
4284     }
4285     int groupId=md->getMemberGroupId();
4286     if (groupId!=-1)
4287     {
4288       auto it = Doxygen::memberGroupInfoMap.find(groupId);
4289       if (it!=Doxygen::memberGroupInfoMap.end())
4290       {
4291         auto &info = it->second;
4292         auto mg_it = std::find_if(pMemberGroups->begin(),
4293                                   pMemberGroups->end(),
4294                                   [&groupId](const auto &g)
4295                                   { return g->groupId()==groupId; }
4296                                  );
4297         MemberGroup *mg_ptr = 0;
4298         if (mg_it==pMemberGroups->end())
4299         {
4300           auto mg = std::make_unique<MemberGroup>(
4301                     context,
4302                     groupId,
4303                     info->header,
4304                     info->doc,
4305                     info->docFile,
4306                     info->docLine,
4307                     ml->container());
4308           mg_ptr = mg.get();
4309           pMemberGroups->push_back(std::move(mg));
4310         }
4311         else
4312         {
4313           mg_ptr = (*mg_it).get();
4314         }
4315         movedMembers.push_back(MoveMemberInfo(md,mg_ptr,info->m_sli));
4316       }
4317     }
4318   }
4319 
4320   // move the members to their group
4321   for (const auto &mmi : movedMembers)
4322   {
4323     ml->remove(mmi.memberDef); // remove from member list
4324     mmi.memberGroup->insertMember(mmi.memberDef->resolveAlias()); // insert in member group
4325     mmi.memberGroup->setRefItems(mmi.sli);
4326     MemberDefMutable *rmdm = toMemberDefMutable(mmi.memberDef);
4327     if (rmdm)
4328     {
4329       rmdm->setMemberGroup(mmi.memberGroup);
4330     }
4331   }
4332 }
4333 
4334 /*! Extracts a (sub-)string from \a type starting at \a pos that
4335  *  could form a class. The index of the match is returned and the found
4336  *  class \a name and a template argument list \a templSpec. If -1 is returned
4337  *  there are no more matches.
4338  */
extractClassNameFromType(const QCString & type,int & pos,QCString & name,QCString & templSpec,SrcLangExt lang)4339 int extractClassNameFromType(const QCString &type,int &pos,QCString &name,QCString &templSpec,SrcLangExt lang)
4340 {
4341   static reg::Ex re_norm(R"(\a[\w:]*)");
4342   static reg::Ex re_fortran(R"(\a[\w:()=]*)");
4343   static const reg::Ex *re = &re_norm;
4344 
4345   name.resize(0);
4346   templSpec.resize(0);
4347   if (type.isEmpty()) return -1;
4348   int typeLen=(int)type.length();
4349   if (typeLen>0)
4350   {
4351     if (lang == SrcLangExt_Fortran)
4352     {
4353       if (type[pos]==',') return -1;
4354       if (QCString(type).left(4).lower()!="type")
4355       {
4356         re = &re_fortran;
4357       }
4358     }
4359     std::string s = type.str();
4360     reg::Iterator it(s,*re,(int)pos);
4361     reg::Iterator end;
4362 
4363     if (it!=end)
4364     {
4365       const auto &match = *it;
4366       int i = (int)match.position();
4367       int l = (int)match.length();
4368       int ts = i+l;
4369       int te = ts;
4370       int tl = 0;
4371 
4372       while (ts<typeLen && type[ts]==' ') ts++,tl++; // skip any whitespace
4373       if (ts<typeLen && type[ts]=='<') // assume template instance
4374       {
4375         // locate end of template
4376         te=ts+1;
4377         int brCount=1;
4378         while (te<typeLen && brCount!=0)
4379         {
4380           if (type[te]=='<')
4381           {
4382             if (te<typeLen-1 && type[te+1]=='<') te++; else brCount++;
4383           }
4384           if (type[te]=='>')
4385           {
4386             if (te<typeLen-1 && type[te+1]=='>') te++; else brCount--;
4387           }
4388           te++;
4389         }
4390       }
4391       name = match.str();
4392       if (te>ts)
4393       {
4394         templSpec = QCString(type).mid(ts,te-ts);
4395         tl+=te-ts;
4396         pos=i+l+tl;
4397       }
4398       else // no template part
4399       {
4400         pos=i+l;
4401       }
4402       //printf("extractClassNameFromType([in] type=%s,[out] pos=%d,[out] name=%s,[out] templ=%s)=TRUE i=%d\n",
4403       //    qPrint(type),pos,qPrint(name),qPrint(templSpec),i);
4404       return i;
4405     }
4406   }
4407   pos = typeLen;
4408   //printf("extractClassNameFromType([in] type=%s,[out] pos=%d,[out] name=%s,[out] templ=%s)=FALSE\n",
4409   //       qPrint(type),pos,qPrint(name),qPrint(templSpec));
4410   return -1;
4411 }
4412 
normalizeNonTemplateArgumentsInString(const QCString & name,const Definition * context,const ArgumentList & formalArgs)4413 QCString normalizeNonTemplateArgumentsInString(
4414        const QCString &name,
4415        const Definition *context,
4416        const ArgumentList &formalArgs)
4417 {
4418   // skip until <
4419   int p=name.find('<');
4420   if (p==-1) return name;
4421   p++;
4422   QCString result = name.left(p);
4423 
4424   std::string s = name.mid(p).str();
4425   static const reg::Ex re(R"([\a:][\w:]*)");
4426   reg::Iterator it(s,re);
4427   reg::Iterator end;
4428   size_t pi=0;
4429   // for each identifier in the template part (e.g. B<T> -> T)
4430   for (; it!=end ; ++it)
4431   {
4432     const auto &match = *it;
4433     size_t i = match.position();
4434     size_t l = match.length();
4435     result += s.substr(pi,i-pi);
4436     QCString n(match.str());
4437     bool found=FALSE;
4438     for (const Argument &formArg : formalArgs)
4439     {
4440       if (formArg.name == n)
4441       {
4442         found=TRUE;
4443         break;
4444       }
4445     }
4446     if (!found)
4447     {
4448       // try to resolve the type
4449       SymbolResolver resolver;
4450       const ClassDef *cd = resolver.resolveClass(context,n);
4451       if (cd)
4452       {
4453         result+=cd->name();
4454       }
4455       else
4456       {
4457         result+=n;
4458       }
4459     }
4460     else
4461     {
4462       result+=n;
4463     }
4464     pi=i+l;
4465   }
4466   result+=s.substr(pi);
4467   //printf("normalizeNonTemplateArgumentInString(%s)=%s\n",qPrint(name),qPrint(result));
4468   return removeRedundantWhiteSpace(result);
4469 }
4470 
4471 
4472 /*! Substitutes any occurrence of a formal argument from argument list
4473  *  \a formalArgs in \a name by the corresponding actual argument in
4474  *  argument list \a actualArgs. The result after substitution
4475  *  is returned as a string. The argument \a name is used to
4476  *  prevent recursive substitution.
4477  */
substituteTemplateArgumentsInString(const QCString & nm,const ArgumentList & formalArgs,const std::unique_ptr<ArgumentList> & actualArgs)4478 QCString substituteTemplateArgumentsInString(
4479     const QCString &nm,
4480     const ArgumentList &formalArgs,
4481     const std::unique_ptr<ArgumentList> &actualArgs)
4482 {
4483   //printf("substituteTemplateArgumentsInString(name=%s formal=%s actualArg=%s)\n",
4484   //    qPrint(nm),qPrint(argListToString(formalArgs)),actualArgs ? qPrint(argListToString(*actualArgs)): "");
4485   if (formalArgs.empty()) return nm;
4486   QCString result;
4487 
4488   static const reg::Ex re(R"(\a[\w:]*)");
4489   std::string name = nm.str();
4490   reg::Iterator it(name,re);
4491   reg::Iterator end;
4492   size_t p=0;
4493 
4494   for (; it!=end ; ++it)
4495   {
4496     const auto &match = *it;
4497     size_t i = match.position();
4498     size_t l = match.length();
4499     if (i>p) result += name.substr(p,i-p);
4500     QCString n(match.str());
4501     ArgumentList::iterator actIt;
4502     if (actualArgs)
4503     {
4504       actIt = actualArgs->begin();
4505     }
4506 
4507     // if n is a template argument, then we substitute it
4508     // for its template instance argument.
4509     bool found=FALSE;
4510     for (auto formIt = formalArgs.begin();
4511         formIt!=formalArgs.end() && !found;
4512         ++formIt
4513         )
4514     {
4515       Argument formArg = *formIt;
4516       Argument actArg;
4517       if (actualArgs && actIt!=actualArgs->end())
4518       {
4519         actArg = *actIt;
4520       }
4521       if (formArg.type.left(6)=="class " && formArg.name.isEmpty())
4522       {
4523         formArg.name = formArg.type.mid(6);
4524         formArg.type = "class";
4525       }
4526       if (formArg.type.left(9)=="typename " && formArg.name.isEmpty())
4527       {
4528         formArg.name = formArg.type.mid(9);
4529         formArg.type = "typename";
4530       }
4531       if (formArg.type=="class" || formArg.type=="typename" || formArg.type.left(8)=="template")
4532       {
4533         //printf("n=%s formArg->type='%s' formArg->name='%s' formArg->defval='%s'\n",
4534         //  qPrint(n),qPrint(formArg->type),qPrint(formArg->name),qPrint(formArg->defval));
4535         //printf(">> n='%s' formArg->name='%s' actArg->type='%s' actArg->name='%s'\n",
4536         //    qPrint(n),qPrint(formArg.name),actIt!=actualArgs.end() ? qPrint(actIt->type) : "",actIt!=actualArgs.end() ? qPrint(actIt->name) : ""
4537         //    );
4538         if (formArg.name==n && actualArgs && actIt!=actualArgs->end() && !actArg.type.isEmpty()) // base class is a template argument
4539         {
4540           // replace formal argument with the actual argument of the instance
4541           if (!leftScopeMatch(actArg.type,n))
4542             // the scope guard is to prevent recursive lockup for
4543             // template<class A> class C : public<A::T>,
4544             // where A::T would become A::T::T here,
4545             // since n==A and actArg->type==A::T
4546             // see bug595833 for an example
4547           {
4548             if (actArg.name.isEmpty())
4549             {
4550               result += actArg.type+" ";
4551               found=TRUE;
4552             }
4553             else
4554               // for case where the actual arg is something like "unsigned int"
4555               // the "int" part is in actArg->name.
4556             {
4557               result += actArg.type+" "+actArg.name+" ";
4558               found=TRUE;
4559             }
4560           }
4561         }
4562         else if (formArg.name==n &&
4563                  (actualArgs==nullptr || actIt==actualArgs->end()) &&
4564                  !formArg.defval.isEmpty() &&
4565                  formArg.defval!=nm /* to prevent recursion */
4566             )
4567         {
4568           result += substituteTemplateArgumentsInString(formArg.defval,formalArgs,actualArgs)+" ";
4569           found=TRUE;
4570         }
4571       }
4572       else if (formArg.name==n &&
4573                (actualArgs==nullptr || actIt==actualArgs->end()) &&
4574                !formArg.defval.isEmpty() &&
4575                formArg.defval!=nm /* to prevent recursion */
4576               )
4577       {
4578         result += substituteTemplateArgumentsInString(formArg.defval,formalArgs,actualArgs)+" ";
4579         found=TRUE;
4580       }
4581       if (actualArgs && actIt!=actualArgs->end())
4582       {
4583         actIt++;
4584       }
4585     }
4586     if (!found)
4587     {
4588       result += n;
4589     }
4590     p=i+l;
4591   }
4592   result+=name.substr(p);
4593   //printf("      Inheritance relation %s -> %s\n",
4594   //    qPrint(name),qPrint(result));
4595   return result.stripWhiteSpace();
4596 }
4597 
4598 
4599 /*! Strips template specifiers from scope \a fullName, except those
4600  *  that make up specialized classes. The switch \a parentOnly
4601  *  determines whether or not a template "at the end" of a scope
4602  *  should be considered, e.g. with \a parentOnly is \c TRUE, \c A<T>::B<S> will
4603  *  try to strip `<T>` and not `<S>`, while \a parentOnly is \c FALSE will
4604  *  strip both unless `A<T>` or `B<S>` are specialized template classes.
4605  */
stripTemplateSpecifiersFromScope(const QCString & fullName,bool parentOnly,QCString * pLastScopeStripped)4606 QCString stripTemplateSpecifiersFromScope(const QCString &fullName,
4607     bool parentOnly,
4608     QCString *pLastScopeStripped)
4609 {
4610   int i=fullName.find('<');
4611   if (i==-1) return fullName;
4612   QCString result;
4613   int p=0;
4614   int l=fullName.length();
4615   while (i!=-1)
4616   {
4617     //printf("1:result+=%s\n",qPrint(fullName.mid(p,i-p)));
4618     int e=i+1;
4619     int count=1;
4620     int round=0;
4621     while (e<l && count>0)
4622     {
4623       char c=fullName.at(e++);
4624       switch (c)
4625       {
4626         case '(': round++; break;
4627         case ')': if (round>0) round--; break;
4628         case '<': if (round==0) count++; break;
4629         case '>': if (round==0) count--; break;
4630         default:
4631           break;
4632       }
4633     }
4634     int si= fullName.find("::",e);
4635 
4636     if (parentOnly && si==-1) break;
4637     // we only do the parent scope, so we stop here if needed
4638 
4639     result+=fullName.mid(p,i-p);
4640     //printf("  trying %s\n",qPrint(result+fullName.mid(i,e-i)));
4641     if (getClass(result+fullName.mid(i,e-i))!=0)
4642     {
4643       result+=fullName.mid(i,e-i);
4644       //printf("  2:result+=%s\n",qPrint(fullName.mid(i,e-i-1)));
4645     }
4646     else if (pLastScopeStripped)
4647     {
4648       //printf("  last stripped scope '%s'\n",qPrint(fullName.mid(i,e-i)));
4649       *pLastScopeStripped=fullName.mid(i,e-i);
4650     }
4651     p=e;
4652     i=fullName.find('<',p);
4653   }
4654   result+=fullName.right(l-p);
4655   //printf("3:result+=%s\n",qPrint(fullName.right(l-p)));
4656   return result;
4657 }
4658 
4659 /*! Merges two scope parts together. The parts may (partially) overlap.
4660  *  Example1: \c A::B and \c B::C will result in \c A::B::C <br>
4661  *  Example2: \c A and \c B will be \c A::B <br>
4662  *  Example3: \c A::B and B will be \c A::B
4663  *
4664  *  @param leftScope the left hand part of the scope.
4665  *  @param rightScope the right hand part of the scope.
4666  *  @returns the merged scope.
4667  */
mergeScopes(const QCString & leftScope,const QCString & rightScope)4668 QCString mergeScopes(const QCString &leftScope,const QCString &rightScope)
4669 {
4670   // case leftScope=="A" rightScope=="A::B" => result = "A::B"
4671   if (leftScopeMatch(rightScope,leftScope)) return rightScope;
4672   QCString result;
4673   int i=0,p=leftScope.length();
4674 
4675   // case leftScope=="A::B" rightScope=="B::C" => result = "A::B::C"
4676   // case leftScope=="A::B" rightScope=="B" => result = "A::B"
4677   bool found=FALSE;
4678   while ((i=leftScope.findRev("::",p))>0)
4679   {
4680     if (leftScopeMatch(rightScope,leftScope.right(leftScope.length()-i-2)))
4681     {
4682       result = leftScope.left(i+2)+rightScope;
4683       found=TRUE;
4684     }
4685     p=i-1;
4686   }
4687   if (found) return result;
4688 
4689   // case leftScope=="A" rightScope=="B" => result = "A::B"
4690   result=leftScope;
4691   if (!result.isEmpty() && !rightScope.isEmpty()) result+="::";
4692   result+=rightScope;
4693   return result;
4694 }
4695 
4696 /*! Returns a fragment from scope \a s, starting at position \a p.
4697  *
4698  *  @param s the scope name as a string.
4699  *  @param p the start position (0 is the first).
4700  *  @param l the resulting length of the fragment.
4701  *  @returns the location of the fragment, or -1 if non is found.
4702  */
getScopeFragment(const QCString & s,int p,int * l)4703 int getScopeFragment(const QCString &s,int p,int *l)
4704 {
4705   int sl=s.length();
4706   int sp=p;
4707   int count=0;
4708   bool done;
4709   if (sp>=sl) return -1;
4710   while (sp<sl)
4711   {
4712     char c=s.at(sp);
4713     if (c==':') sp++,p++; else break;
4714   }
4715   while (sp<sl)
4716   {
4717     char c=s.at(sp);
4718     switch (c)
4719     {
4720       case ':': // found next part
4721         goto found;
4722       case '<': // skip template specifier
4723         count=1;sp++;
4724         done=FALSE;
4725         while (sp<sl && !done)
4726         {
4727           // TODO: deal with << and >> operators!
4728           c=s.at(sp++);
4729           switch(c)
4730           {
4731             case '<': count++; break;
4732             case '>': count--; if (count==0) done=TRUE; break;
4733             default: break;
4734           }
4735         }
4736         break;
4737       default:
4738         sp++;
4739         break;
4740     }
4741   }
4742 found:
4743   *l=sp-p;
4744   //printf("getScopeFragment(%s,%d)=%s\n",qPrint(s),p,qPrint(s.mid(p,*l)));
4745   return p;
4746 }
4747 
4748 //----------------------------------------------------------------------------
4749 
addRelatedPage(const QCString & name,const QCString & ptitle,const QCString & doc,const QCString & fileName,int docLine,int startLine,const RefItemVector & sli,GroupDef * gd,const TagInfo * tagInfo,bool xref,SrcLangExt lang)4750 PageDef *addRelatedPage(const QCString &name,const QCString &ptitle,
4751     const QCString &doc,
4752     const QCString &fileName,
4753     int docLine,
4754     int startLine,
4755     const RefItemVector &sli,
4756     GroupDef *gd,
4757     const TagInfo *tagInfo,
4758     bool xref,
4759     SrcLangExt lang
4760     )
4761 {
4762   PageDef *pd=0;
4763   //printf("addRelatedPage(name=%s gd=%p)\n",qPrint(name),gd);
4764   QCString title=ptitle.stripWhiteSpace();
4765   bool newPage = true;
4766   if ((pd=Doxygen::pageLinkedMap->find(name)) && !pd->isReference())
4767   {
4768     if (!xref && !title.isEmpty() && pd->title()!=pd->name() && pd->title()!=title)
4769     {
4770       warn(fileName,startLine,"multiple use of page label '%s' with different titles, (other occurrence: %s, line: %d)",
4771          qPrint(name),qPrint(pd->docFile()),pd->getStartBodyLine());
4772     }
4773     if (!title.isEmpty() && pd->title()==pd->name()) // pd has no real title yet
4774     {
4775       pd->setTitle(title);
4776       SectionInfo *si = SectionManager::instance().find(pd->name());
4777       if (si)
4778       {
4779         si->setTitle(title);
4780       }
4781     }
4782     // append documentation block to the page.
4783     pd->setDocumentation(doc,fileName,docLine);
4784     //printf("Adding page docs '%s' pi=%p name=%s\n",qPrint(doc),pd,name);
4785     // append (x)refitems to the page.
4786     pd->setRefItems(sli);
4787     newPage = false;
4788   }
4789 
4790   if (newPage) // new page
4791   {
4792     QCString baseName=name;
4793     if (baseName.right(4)==".tex")
4794       baseName=baseName.left(baseName.length()-4);
4795     else if (baseName.right(Doxygen::htmlFileExtension.length())==Doxygen::htmlFileExtension)
4796       baseName=baseName.left(baseName.length()-Doxygen::htmlFileExtension.length());
4797 
4798     //printf("Appending page '%s'\n",qPrint(baseName));
4799     if (pd) // replace existing page
4800     {
4801       pd->setDocumentation(doc,fileName,docLine);
4802       pd->setFileName(::convertNameToFile(baseName,FALSE,TRUE));
4803       pd->setShowLineNo(FALSE);
4804       pd->setNestingLevel(0);
4805       pd->setPageScope(0);
4806       pd->setTitle(title);
4807     }
4808     else // newPage
4809     {
4810       pd = Doxygen::pageLinkedMap->add(baseName,
4811           std::unique_ptr<PageDef>(
4812              createPageDef(fileName,docLine,baseName,doc,title)));
4813     }
4814     pd->setBodySegment(startLine,startLine,-1);
4815 
4816     pd->setRefItems(sli);
4817     pd->setLanguage(lang);
4818 
4819     if (tagInfo)
4820     {
4821       pd->setReference(tagInfo->tagName);
4822       pd->setFileName(tagInfo->fileName);
4823     }
4824 
4825 
4826     if (gd) gd->addPage(pd);
4827 
4828     if (pd->hasTitle())
4829     {
4830       //outputList->writeTitle(pi->name,pi->title);
4831 
4832       // a page name is a label as well!
4833       QCString file;
4834       QCString orgFile;
4835       int line  = -1;
4836       if (gd)
4837       {
4838         file=gd->getOutputFileBase();
4839         orgFile=gd->getOutputFileBase();
4840       }
4841       else
4842       {
4843         file=pd->getOutputFileBase();
4844         orgFile=pd->docFile();
4845         line = pd->getStartBodyLine();
4846       }
4847       const SectionInfo *si = SectionManager::instance().find(pd->name());
4848       if (si)
4849       {
4850         if (!si->ref().isEmpty()) // we are from a tag file
4851         {
4852           SectionManager::instance().replace(pd->name(),
4853               file,-1,pd->title(),SectionType::Page,0,pd->getReference());
4854         }
4855         else if (si->lineNr() != -1)
4856         {
4857           warn(orgFile,line,"multiple use of section label '%s', (first occurrence: %s, line %d)",qPrint(pd->name()),qPrint(si->fileName()),si->lineNr());
4858         }
4859         else
4860         {
4861           warn(orgFile,line,"multiple use of section label '%s', (first occurrence: %s)",qPrint(pd->name()),qPrint(si->fileName()));
4862         }
4863       }
4864       else
4865       {
4866         SectionManager::instance().add(pd->name(),
4867             file,-1,pd->title(),SectionType::Page,0,pd->getReference());
4868         //printf("si->label='%s' si->definition=%s si->fileName='%s'\n",
4869         //      qPrint(si->label),si->definition?si->definition->name().data():"<none>",
4870         //      qPrint(si->fileName));
4871         //printf("  SectionInfo: sec=%p sec->fileName=%s\n",si,qPrint(si->fileName));
4872         //printf("Adding section key=%s si->fileName=%s\n",qPrint(pageName),qPrint(si->fileName));
4873       }
4874     }
4875   }
4876   return pd;
4877 }
4878 
4879 //----------------------------------------------------------------------------
4880 
addRefItem(const RefItemVector & sli,const QCString & key,const QCString & prefix,const QCString & name,const QCString & title,const QCString & args,const Definition * scope)4881 void addRefItem(const RefItemVector &sli,
4882     const QCString &key, const QCString &prefix, const QCString &name,
4883     const QCString &title, const QCString &args, const Definition *scope)
4884 {
4885   //printf("addRefItem(sli=%d,key=%s,prefix=%s,name=%s,title=%s,args=%s)\n",(int)sli.size(),key,prefix,name,title,args);
4886   if (!key.isEmpty() && key[0]!='@') // check for @ to skip anonymous stuff (see bug427012)
4887   {
4888     for (RefItem *item : sli)
4889     {
4890         item->setPrefix(prefix);
4891         item->setScope(scope);
4892         item->setName(name);
4893         item->setTitle(title);
4894         item->setArgs(args);
4895         item->setGroup(key);
4896     }
4897   }
4898 }
4899 
recursivelyAddGroupListToTitle(OutputList & ol,const Definition * d,bool root)4900 bool recursivelyAddGroupListToTitle(OutputList &ol,const Definition *d,bool root)
4901 {
4902   if (!d->partOfGroups().empty()) // write list of group to which this definition belongs
4903   {
4904     if (root)
4905     {
4906       ol.pushGeneratorState();
4907       ol.disableAllBut(OutputGenerator::Html);
4908       ol.writeString("<div class=\"ingroups\">");
4909     }
4910     bool first=true;
4911     for (const auto &gd : d->partOfGroups())
4912     {
4913       if (!first) { ol.writeString(" &#124; "); } else first=false;
4914       if (recursivelyAddGroupListToTitle(ol, gd, FALSE))
4915       {
4916         ol.writeString(" &raquo; ");
4917       }
4918       ol.writeObjectLink(gd->getReference(),gd->getOutputFileBase(),QCString(),gd->groupTitle());
4919     }
4920     if (root)
4921     {
4922       ol.writeString("</div>");
4923       ol.popGeneratorState();
4924     }
4925     return true;
4926   }
4927   return false;
4928 }
4929 
addGroupListToTitle(OutputList & ol,const Definition * d)4930 void addGroupListToTitle(OutputList &ol,const Definition *d)
4931 {
4932   recursivelyAddGroupListToTitle(ol,d,TRUE);
4933 }
4934 
filterLatexString(TextStream & t,const QCString & str,bool insideTabbing,bool insidePre,bool insideItem,bool insideTable,bool keepSpaces,const bool retainNewline)4935 void filterLatexString(TextStream &t,const QCString &str,
4936     bool insideTabbing,bool insidePre,bool insideItem,bool insideTable,bool keepSpaces, const bool retainNewline)
4937 {
4938   if (str.isEmpty()) return;
4939   //if (strlen(str)<2) stackTrace();
4940   const unsigned char *p=(const unsigned char *)str.data();
4941   const unsigned char *q;
4942   int cnt;
4943   unsigned char c;
4944   unsigned char pc='\0';
4945   while (*p)
4946   {
4947     c=*p++;
4948 
4949     if (insidePre)
4950     {
4951       switch(c)
4952       {
4953         case 0xef: // handle U+FFFD i.e. "Replacement character" caused by octal: 357 277 275 / hexadecimal 0xef 0xbf 0xbd
4954                    // the LaTeX command \ucr has been defined in doxygen.sty
4955           if ((unsigned char)*(p) == 0xbf && (unsigned char)*(p+1) == 0xbd)
4956           {
4957             t << "{\\ucr}";
4958             p += 2;
4959           }
4960           else
4961             t << (char)c;
4962           break;
4963         case '\\': t << "\\(\\backslash\\)"; break;
4964         case '{':  t << "\\{"; break;
4965         case '}':  t << "\\}"; break;
4966         case '_':  t << "\\_"; break;
4967         case '&':  t << "\\&"; break;
4968         case '%':  t << "\\%"; break;
4969         case '#':  t << "\\#"; break;
4970         case '$':  t << "\\$"; break;
4971         case '"':  t << "\"{}"; break;
4972         case '-':  t << "-\\/"; break;
4973         case '^':  insideTable ? t << "\\string^" : t << (char)c;    break;
4974         case '~':  t << "\\string~";    break;
4975         case '\n':  if (retainNewline) t << "\\newline"; else t << ' ';
4976                    break;
4977         case ' ':  if (keepSpaces) t << "~"; else t << ' ';
4978                    break;
4979         default:
4980                    if (c<32) t << ' '; // non printable control character
4981                    else t << (char)c;
4982                    break;
4983       }
4984     }
4985     else
4986     {
4987       switch(c)
4988       {
4989         case 0xef: // handle U+FFFD i.e. "Replacement character" caused by octal: 357 277 275 / hexadecimal 0xef 0xbf 0xbd
4990                    // the LaTeX command \ucr has been defined in doxygen.sty
4991           if ((unsigned char)*(p) == 0xbf && (unsigned char)*(p+1) == 0xbd)
4992           {
4993             t << "{\\ucr}";
4994             p += 2;
4995           }
4996           else
4997             t << (char)c;
4998           break;
4999         case '#':  t << "\\#";           break;
5000         case '$':  t << "\\$";           break;
5001         case '%':  t << "\\%";           break;
5002         case '^':  t << "$^\\wedge$";    break;
5003         case '&':  // possibility to have a special symbol
5004                    q = p;
5005                    cnt = 2; // we have to count & and ; as well
5006                    while ((*q >= 'a' && *q <= 'z') || (*q >= 'A' && *q <= 'Z') || (*q >= '0' && *q <= '9'))
5007                    {
5008                      cnt++;
5009                      q++;
5010                    }
5011                    if (*q == ';')
5012                    {
5013                       --p; // we need & as well
5014                       DocSymbol::SymType res = HtmlEntityMapper::instance()->name2sym(QCString((char *)p).left(cnt));
5015                       if (res == DocSymbol::Sym_Unknown)
5016                       {
5017                         p++;
5018                         t << "\\&";
5019                       }
5020                       else
5021                       {
5022                         t << HtmlEntityMapper::instance()->latex(res);
5023                         q++;
5024                         p = q;
5025                       }
5026                    }
5027                    else
5028                    {
5029                      t << "\\&";
5030                    }
5031                    break;
5032         case '*':  t << "$\\ast$";       break;
5033         case '_':  if (!insideTabbing) t << "\\+";
5034                    t << "\\_";
5035                    if (!insideTabbing) t << "\\+";
5036                    break;
5037         case '{':  t << "\\{";           break;
5038         case '}':  t << "\\}";           break;
5039         case '<':  t << "$<$";           break;
5040         case '>':  t << "$>$";           break;
5041         case '|':  t << "$\\vert$";      break;
5042         case '~':  t << "$\\sim$";       break;
5043         case '[':  if (Config_getBool(PDF_HYPERLINKS) || insideItem)
5044                      t << "\\mbox{[}";
5045                    else
5046                      t << "[";
5047                    break;
5048         case ']':  if (pc=='[') t << "$\\,$";
5049                      if (Config_getBool(PDF_HYPERLINKS) || insideItem)
5050                        t << "\\mbox{]}";
5051                      else
5052                        t << "]";
5053                    break;
5054         case '-':  t << "-\\/";
5055                    break;
5056         case '\\': t << "\\textbackslash{}";
5057                    break;
5058         case '"':  t << "\\char`\\\"{}";
5059                    break;
5060         case '`':  t << "\\`{}";
5061                    break;
5062         case '\'': t << "\\textquotesingle{}";
5063                    break;
5064         case '\n':  if (retainNewline) t << "\\newline"; else t << ' ';
5065                    break;
5066         case ' ':  if (keepSpaces) { if (insideTabbing) t << "\\>"; else t << '~'; } else t << ' ';
5067                    break;
5068 
5069         default:
5070                    //if (!insideTabbing && forceBreaks && c!=' ' && *p!=' ')
5071                    if (!insideTabbing &&
5072                        ((c>='A' && c<='Z' && pc!=' ' && !(pc>='A' && pc <= 'Z') && pc!='\0' && *p) || (c==':' && pc!=':') || (pc=='.' && isId(c)))
5073                       )
5074                    {
5075                      t << "\\+";
5076                    }
5077                    if (c<32)
5078                    {
5079                      t << ' '; // non-printable control character
5080                    }
5081                    else
5082                    {
5083                      t << (char)c;
5084                    }
5085       }
5086     }
5087     pc = c;
5088   }
5089 }
5090 
latexEscapeLabelName(const QCString & s)5091 QCString latexEscapeLabelName(const QCString &s)
5092 {
5093   if (s.isEmpty()) return s;
5094   QCString tmp(s.length()+1);
5095   TextStream t;
5096   const char *p=s.data();
5097   char c;
5098   int i;
5099   while ((c=*p++))
5100   {
5101     switch (c)
5102     {
5103       case '|': t << "\\texttt{\"|}"; break;
5104       case '!': t << "\"!"; break;
5105       case '@': t << "\"@"; break;
5106       case '%': t << "\\%";       break;
5107       case '{': t << "\\lcurly{}"; break;
5108       case '}': t << "\\rcurly{}"; break;
5109       case '~': t << "````~"; break; // to get it a bit better in index together with other special characters
5110       // NOTE: adding a case here, means adding it to while below as well!
5111       default:
5112         i=0;
5113         // collect as long string as possible, before handing it to docify
5114         tmp[i++]=c;
5115         while ((c=*p) && c!='@' && c!='[' && c!=']' && c!='!' && c!='{' && c!='}' && c!='|')
5116         {
5117           tmp[i++]=c;
5118           p++;
5119         }
5120         tmp[i]=0;
5121         filterLatexString(t,tmp,
5122                           true,  // insideTabbing
5123                           false, // insidePre
5124                           false, // insideItem
5125                           false, // insideTable
5126                           false  // keepSpaces
5127                          );
5128         break;
5129     }
5130   }
5131   return t.str();
5132 }
5133 
latexEscapeIndexChars(const QCString & s)5134 QCString latexEscapeIndexChars(const QCString &s)
5135 {
5136   if (s.isEmpty()) return s;
5137   QCString tmp(s.length()+1);
5138   TextStream t;
5139   const char *p=s.data();
5140   char c;
5141   int i;
5142   while ((c=*p++))
5143   {
5144     switch (c)
5145     {
5146       case '!': t << "\"!"; break;
5147       case '"': t << "\"\""; break;
5148       case '@': t << "\"@"; break;
5149       case '|': t << "\\texttt{\"|}"; break;
5150       case '[': t << "["; break;
5151       case ']': t << "]"; break;
5152       case '{': t << "\\lcurly{}"; break;
5153       case '}': t << "\\rcurly{}"; break;
5154       // NOTE: adding a case here, means adding it to while below as well!
5155       default:
5156         i=0;
5157         // collect as long string as possible, before handing it to docify
5158         tmp[i++]=c;
5159         while ((c=*p) && c!='"' && c!='@' && c!='[' && c!=']' && c!='!' && c!='{' && c!='}' && c!='|')
5160         {
5161           tmp[i++]=c;
5162           p++;
5163         }
5164         tmp[i]=0;
5165         filterLatexString(t,tmp,
5166                           true,   // insideTabbing
5167                           false,  // insidePre
5168                           false,  // insideItem
5169                           false,  // insideTable
5170                           false   // keepSpaces
5171                          );
5172         break;
5173     }
5174   }
5175   return t.str();
5176 }
5177 
latexEscapePDFString(const QCString & s)5178 QCString latexEscapePDFString(const QCString &s)
5179 {
5180   if (s.isEmpty()) return s;
5181   TextStream t;
5182   const char *p=s.data();
5183   char c;
5184   while ((c=*p++))
5185   {
5186     switch (c)
5187     {
5188       case '\\': t << "\\textbackslash{}"; break;
5189       case '{':  t << "\\{"; break;
5190       case '}':  t << "\\}"; break;
5191       case '_':  t << "\\_"; break;
5192       case '%':  t << "\\%"; break;
5193       case '&':  t << "\\&"; break;
5194       default:
5195         t << c;
5196         break;
5197     }
5198   }
5199   return t.str();
5200 }
5201 
latexFilterURL(const QCString & s)5202 QCString latexFilterURL(const QCString &s)
5203 {
5204   if (s.isEmpty()) return s;
5205   TextStream t;
5206   const signed char *p=(const signed char*)s.data();
5207   char c;
5208   while ((c=*p++))
5209   {
5210     switch (c)
5211     {
5212       case '#':  t << "\\#"; break;
5213       case '%':  t << "\\%"; break;
5214       case '\\':  t << "\\\\"; break;
5215       default:
5216         if (c<0)
5217         {
5218           unsigned char id = (unsigned char)c;
5219           t << "\\%" << hex[id>>4] << hex[id&0xF];
5220         }
5221         else
5222         {
5223           t << c;
5224         }
5225         break;
5226     }
5227   }
5228   return t.str();
5229 }
5230 
5231 static std::mutex g_rtfFormatMutex;
5232 static std::unordered_map<std::string,std::string> g_tagMap;
5233 static QCString g_nextTag( "AAAAAAAAAA" );
5234 
rtfFormatBmkStr(const QCString & name)5235 QCString rtfFormatBmkStr(const QCString &name)
5236 {
5237   std::lock_guard<std::mutex> lock(g_rtfFormatMutex);
5238 
5239   // To overcome the 40-character tag limitation, we
5240   // substitute a short arbitrary string for the name
5241   // supplied, and keep track of the correspondence
5242   // between names and strings.
5243   auto it = g_tagMap.find(name.str());
5244   if (it!=g_tagMap.end()) // already known
5245   {
5246     return QCString(it->second);
5247   }
5248 
5249   QCString tag = g_nextTag;
5250   auto result = g_tagMap.insert( std::make_pair(name.str(), g_nextTag.str()) );
5251 
5252   if (result.second) // new item was added
5253   {
5254     // increment the next tag.
5255 
5256     char* nxtTag = g_nextTag.rawData() + g_nextTag.length() - 1;
5257     for ( unsigned int i = 0; i < g_nextTag.length(); ++i, --nxtTag )
5258     {
5259       if ( ( ++(*nxtTag) ) > 'Z' )
5260       {
5261         *nxtTag = 'A';
5262       }
5263       else
5264       {
5265         // Since there was no carry, we can stop now
5266         break;
5267       }
5268     }
5269   }
5270 
5271   Debug::print(Debug::Rtf,0,"Name = %s RTF_tag = %s\n",qPrint(name),qPrint(tag));
5272   return tag;
5273 }
5274 
checkExtension(const QCString & fName,const QCString & ext)5275 bool checkExtension(const QCString &fName, const QCString &ext)
5276 {
5277   return fName.right(ext.length())==ext;
5278 }
5279 
addHtmlExtensionIfMissing(const QCString & fName)5280 QCString addHtmlExtensionIfMissing(const QCString &fName)
5281 {
5282   if (fName.isEmpty()) return fName;
5283   if (fName.find('.')==-1) // no extension
5284   {
5285     return QCString(fName)+Doxygen::htmlFileExtension;
5286   }
5287   return fName;
5288 }
5289 
stripExtensionGeneral(const QCString & fName,const QCString & ext)5290 QCString stripExtensionGeneral(const QCString &fName, const QCString &ext)
5291 {
5292   QCString result=fName;
5293   if (result.right(ext.length())==ext)
5294   {
5295     result=result.left(result.length()-ext.length());
5296   }
5297   return result;
5298 }
5299 
stripExtension(const QCString & fName)5300 QCString stripExtension(const QCString &fName)
5301 {
5302   return stripExtensionGeneral(fName, Doxygen::htmlFileExtension);
5303 }
5304 
replaceNamespaceAliases(QCString & scope,int i)5305 void replaceNamespaceAliases(QCString &scope,int i)
5306 {
5307   while (i>0)
5308   {
5309     QCString ns = scope.left(i);
5310     if (!ns.isEmpty())
5311     {
5312       auto it = Doxygen::namespaceAliasMap.find(ns.str());
5313       if (it!=Doxygen::namespaceAliasMap.end())
5314       {
5315         scope=QCString(it->second)+scope.right(scope.length()-i);
5316         i=static_cast<int>(it->second.length());
5317       }
5318     }
5319     if (i>0 && ns==scope.left(i)) break;
5320   }
5321 }
5322 
stripPath(const QCString & s)5323 QCString stripPath(const QCString &s)
5324 {
5325   QCString result=s;
5326   int i=result.findRev('/');
5327   if (i!=-1)
5328   {
5329     result=result.mid(i+1);
5330   }
5331   i=result.findRev('\\');
5332   if (i!=-1)
5333   {
5334     result=result.mid(i+1);
5335   }
5336   return result;
5337 }
5338 
5339 /** returns \c TRUE iff string \a s contains word \a w */
containsWord(const QCString & str,const char * word)5340 bool containsWord(const QCString &str,const char *word)
5341 {
5342   if (str.isEmpty() || word==0) return false;
5343   static const reg::Ex re(R"(\a+)");
5344   std::string s = str.str();
5345   for (reg::Iterator it(s,re) ; it!=reg::Iterator() ; ++it)
5346   {
5347     if (it->str()==word) return true;
5348   }
5349   return false;
5350 }
5351 
5352 /** removes occurrences of whole \a word from \a sentence,
5353  *  while keeps internal spaces and reducing multiple sequences of spaces.
5354  *  Example: sentence=` cat+ catfish cat cat concat cat`, word=`cat` returns: `+ catfish concat`
5355  */
findAndRemoveWord(QCString & sentence,const char * word)5356 bool findAndRemoveWord(QCString &sentence,const char *word)
5357 {
5358   static reg::Ex re(R"(\s*(\<\a+\>)\s*)");
5359   std::string s = sentence.str();
5360   reg::Iterator it(s,re);
5361   reg::Iterator end;
5362   std::string result;
5363   bool found=false;
5364   size_t p=0;
5365   for ( ; it!=end ; ++it)
5366   {
5367     const auto match = *it;
5368     std::string part = match[1].str();
5369     if (part!=word)
5370     {
5371       size_t i = match.position();
5372       size_t l = match.length();
5373       result+=s.substr(p,i-p);
5374       result+=match.str();
5375       p=i+l;
5376     }
5377     else
5378     {
5379       found=true;
5380       size_t i = match[1].position();
5381       size_t l = match[1].length();
5382       result+=s.substr(p,i-p);
5383       p=i+l;
5384     }
5385   }
5386   result+=s.substr(p);
5387   sentence = QCString(result).simplifyWhiteSpace();
5388   return found;
5389 }
5390 
5391 /** Special version of QCString::stripWhiteSpace() that only strips
5392  *  completely blank lines.
5393  *  @param s the string to be stripped
5394  *  @param docLine the line number corresponding to the start of the
5395  *         string. This will be adjusted based on the number of lines stripped
5396  *         from the start.
5397  *  @returns The stripped string.
5398  */
stripLeadingAndTrailingEmptyLines(const QCString & s,int & docLine)5399 QCString stripLeadingAndTrailingEmptyLines(const QCString &s,int &docLine)
5400 {
5401   if (s.isEmpty()) return QCString();
5402   const char *p = s.data();
5403 
5404   // search for leading empty lines
5405   int i=0,li=-1,l=s.length();
5406   char c;
5407   while ((c=*p))
5408   {
5409     if (c==' ' || c=='\t' || c=='\r') i++,p++;
5410     else if (c=='\\' && qstrncmp(p,"\\ilinebr",8)==0) i+=8,li=i,p+=8;
5411     else if (c=='\n') i++,li=i,docLine++,p++;
5412     else break;
5413   }
5414 
5415   // search for trailing empty lines
5416   int b=l-1,bi=-1;
5417   p=s.data()+b;
5418   while (b>=0)
5419   {
5420     c=*p;
5421     if (c==' ' || c=='\t' || c=='\r') b--,p--;
5422     else if (c=='r' && b>=7 && qstrncmp(p-7,"\\ilinebr",8)==0) bi=b-7,b-=8,p-=8;
5423     else if (c=='\n') bi=b,b--,p--;
5424     else break;
5425   }
5426 
5427   // return whole string if no leading or trailing lines where found
5428   if (li==-1 && bi==-1) return s;
5429 
5430   // return substring
5431   if (bi==-1) bi=l;
5432   if (li==-1) li=0;
5433   if (bi<=li) return QCString(); // only empty lines
5434   //printf("docLine='%s' len=%d li=%d bi=%d\n",qPrint(s),s.length(),li,bi);
5435   return s.mid(li,bi-li);
5436 }
5437 
5438 //--------------------------------------------------------------------------
5439 
5440 static std::unordered_map<std::string,int> g_extLookup;
5441 
5442 static struct Lang2ExtMap
5443 {
5444   const char *langName;
5445   const char *parserName;
5446   SrcLangExt parserId;
5447   const char *defExt;
5448 }
5449 g_lang2extMap[] =
5450 {
5451 //  language       parser           parser option
5452   { "idl",         "c",             SrcLangExt_IDL,      ".idl" },
5453   { "java",        "c",             SrcLangExt_Java,     ".java"},
5454   { "javascript",  "c",             SrcLangExt_JS,       ".js"  },
5455   { "csharp",      "c",             SrcLangExt_CSharp,   ".cs"  },
5456   { "d",           "c",             SrcLangExt_D,        ".d"   },
5457   { "php",         "c",             SrcLangExt_PHP,      ".php" },
5458   { "objective-c", "c",             SrcLangExt_ObjC,     ".m"   },
5459   { "c",           "c",             SrcLangExt_Cpp,      ".c"   },
5460   { "c++",         "c",             SrcLangExt_Cpp,      ".cpp" },
5461   { "slice",       "c",             SrcLangExt_Slice,    ".ice" },
5462   { "python",      "python",        SrcLangExt_Python,   ".py"  },
5463   { "fortran",     "fortran",       SrcLangExt_Fortran,  ".f"   },
5464   { "fortranfree", "fortranfree",   SrcLangExt_Fortran,  ".f90" },
5465   { "fortranfixed", "fortranfixed", SrcLangExt_Fortran,  ".f"   },
5466   { "vhdl",        "vhdl",          SrcLangExt_VHDL,     ".vhdl"},
5467   { "xml",         "xml",           SrcLangExt_XML,      ".xml" },
5468   { "sql",         "sql",           SrcLangExt_SQL,      ".sql" },
5469   { "md",          "md",            SrcLangExt_Markdown, ".md"  },
5470   { "lex",         "lex",           SrcLangExt_Lex,      ".l"   },
5471   { 0,             0,              (SrcLangExt)0,        0      }
5472 };
5473 
updateLanguageMapping(const QCString & extension,const QCString & language)5474 bool updateLanguageMapping(const QCString &extension,const QCString &language)
5475 {
5476   const Lang2ExtMap *p = g_lang2extMap;
5477   QCString langName = language.lower();
5478   while (p->langName)
5479   {
5480     if (langName==p->langName) break;
5481     p++;
5482   }
5483   if (!p->langName) return FALSE;
5484 
5485   // found the language
5486   SrcLangExt parserId = p->parserId;
5487   QCString extName = extension.lower();
5488   if (extName.isEmpty()) return FALSE;
5489   if (extName.at(0)!='.') extName.prepend(".");
5490   auto it = g_extLookup.find(extName.str());
5491   if (it!=g_extLookup.end())
5492   {
5493     g_extLookup.erase(it); // language was already register for this ext
5494   }
5495   //printf("registering extension %s\n",qPrint(extName));
5496   g_extLookup.insert(std::make_pair(extName.str(),parserId));
5497   if (!Doxygen::parserManager->registerExtension(extName,p->parserName))
5498   {
5499     err("Failed to assign extension %s to parser %s for language %s\n",
5500         extName.data(),p->parserName,qPrint(language));
5501   }
5502   else
5503   {
5504     //msg("Registered extension %s to language parser %s...\n",
5505     //    extName.data(),qPrint(language));
5506   }
5507   return TRUE;
5508 }
5509 
initDefaultExtensionMapping()5510 void initDefaultExtensionMapping()
5511 {
5512   // NOTE: when adding an extension, also add the extension in config.xml
5513   //                  extension      parser id
5514   updateLanguageMapping(".dox",      "c");
5515   updateLanguageMapping(".txt",      "c"); // see bug 760836
5516   updateLanguageMapping(".doc",      "c");
5517   updateLanguageMapping(".c",        "c");
5518   updateLanguageMapping(".C",        "c");
5519   updateLanguageMapping(".cc",       "c");
5520   updateLanguageMapping(".CC",       "c");
5521   updateLanguageMapping(".cxx",      "c");
5522   updateLanguageMapping(".cpp",      "c");
5523   updateLanguageMapping(".c++",      "c");
5524   updateLanguageMapping(".ii",       "c");
5525   updateLanguageMapping(".ixx",      "c");
5526   updateLanguageMapping(".ipp",      "c");
5527   updateLanguageMapping(".i++",      "c");
5528   updateLanguageMapping(".inl",      "c");
5529   updateLanguageMapping(".h",        "c");
5530   updateLanguageMapping(".H",        "c");
5531   updateLanguageMapping(".hh",       "c");
5532   updateLanguageMapping(".HH",       "c");
5533   updateLanguageMapping(".hxx",      "c");
5534   updateLanguageMapping(".hpp",      "c");
5535   updateLanguageMapping(".h++",      "c");
5536   updateLanguageMapping(".idl",      "idl");
5537   updateLanguageMapping(".ddl",      "idl");
5538   updateLanguageMapping(".odl",      "idl");
5539   updateLanguageMapping(".java",     "java");
5540   //updateLanguageMapping(".as",       "javascript"); // not officially supported
5541   //updateLanguageMapping(".js",       "javascript"); // not officially supported
5542   updateLanguageMapping(".cs",       "csharp");
5543   updateLanguageMapping(".d",        "d");
5544   updateLanguageMapping(".php",      "php");
5545   updateLanguageMapping(".php4",     "php");
5546   updateLanguageMapping(".php5",     "php");
5547   updateLanguageMapping(".inc",      "php");
5548   updateLanguageMapping(".phtml",    "php");
5549   updateLanguageMapping(".m",        "objective-c");
5550   updateLanguageMapping(".M",        "objective-c");
5551   updateLanguageMapping(".mm",       "c");  // see bug746361
5552   updateLanguageMapping(".py",       "python");
5553   updateLanguageMapping(".pyw",      "python");
5554   updateLanguageMapping(".f",        "fortran");
5555   updateLanguageMapping(".for",      "fortran");
5556   updateLanguageMapping(".f90",      "fortran");
5557   updateLanguageMapping(".f95",      "fortran");
5558   updateLanguageMapping(".f03",      "fortran");
5559   updateLanguageMapping(".f08",      "fortran");
5560   updateLanguageMapping(".f18",      "fortran");
5561   updateLanguageMapping(".vhd",      "vhdl");
5562   updateLanguageMapping(".vhdl",     "vhdl");
5563   updateLanguageMapping(".ucf",      "vhdl");
5564   updateLanguageMapping(".qsf",      "vhdl");
5565   updateLanguageMapping(".md",       "md");
5566   updateLanguageMapping(".markdown", "md");
5567   updateLanguageMapping(".ice",      "slice");
5568   updateLanguageMapping(".l",        "lex");
5569   updateLanguageMapping(".doxygen_lex_c", "c"); // this is a placeholder so we can map initializations
5570                                                 // in the lex scanning to cpp
5571 }
5572 
addCodeOnlyMappings()5573 void addCodeOnlyMappings()
5574 {
5575   updateLanguageMapping(".xml",   "xml");
5576   updateLanguageMapping(".sql",   "sql");
5577 }
5578 
getLanguageFromFileName(const QCString & fileName,SrcLangExt defLang)5579 SrcLangExt getLanguageFromFileName(const QCString& fileName, SrcLangExt defLang)
5580 {
5581   FileInfo fi(fileName.str());
5582   // we need only the part after the last ".", newer implementations of FileInfo have 'suffix()' for this.
5583   QCString extName = QCString(fi.extension(FALSE)).lower();
5584   if (extName.isEmpty()) extName=".no_extension";
5585   if (extName.at(0)!='.') extName.prepend(".");
5586   auto it = g_extLookup.find(extName.str());
5587   if (it!=g_extLookup.end()) // listed extension
5588   {
5589     //printf("getLanguageFromFileName(%s)=%x\n",qPrint(fi.extension()),*pVal);
5590     return (SrcLangExt)it->second;
5591   }
5592   //printf("getLanguageFromFileName(%s) not found!\n",qPrint(fileName));
5593   return defLang; // not listed => assume C-ish language.
5594 }
5595 
5596 /// Routine to handle the language attribute of the `\code` command
getLanguageFromCodeLang(QCString & fileName)5597 SrcLangExt getLanguageFromCodeLang(QCString &fileName)
5598 {
5599   // try the extension
5600   SrcLangExt lang = getLanguageFromFileName(fileName, SrcLangExt_Unknown);
5601   if (lang == SrcLangExt_Unknown)
5602   {
5603     // try the language names
5604     const Lang2ExtMap *p = g_lang2extMap;
5605     QCString langName = fileName.lower();
5606     if (langName.at(0)=='.') langName = langName.mid(1);
5607     while (p->langName)
5608     {
5609       if (langName==p->langName)
5610       {
5611         // found the language
5612         lang     = p->parserId;
5613         fileName = p->defExt;
5614         break;
5615       }
5616       p++;
5617     }
5618     if (!p->langName)
5619     {
5620       return SrcLangExt_Cpp;
5621     }
5622   }
5623   return lang;
5624 }
5625 
getFileNameExtension(const QCString & fn)5626 QCString getFileNameExtension(const QCString &fn)
5627 {
5628   if (fn.isEmpty()) return "";
5629   int lastDot = fn.findRev('.');
5630   if (lastDot!=-1) return fn.mid(lastDot);
5631   return "";
5632 }
5633 
5634 //--------------------------------------------------------------------------
5635 
getMemberFromSymbol(const Definition * scope,const FileDef * fileScope,const QCString & n)5636 static MemberDef *getMemberFromSymbol(const Definition *scope,const FileDef *fileScope,
5637                                 const QCString &n)
5638 {
5639   if (scope==0 ||
5640       (scope->definitionType()!=Definition::TypeClass &&
5641        scope->definitionType()!=Definition::TypeNamespace
5642       )
5643      )
5644   {
5645     scope=Doxygen::globalScope;
5646   }
5647 
5648   QCString name = n;
5649   if (name.isEmpty())
5650     return 0; // no name was given
5651 
5652   auto range = Doxygen::symbolMap->find(name);
5653   if (range.first==range.second)
5654     return 0; // could not find any matching symbols
5655 
5656   // mostly copied from getResolvedClassRec()
5657   QCString explicitScopePart;
5658   int qualifierIndex = computeQualifiedIndex(name);
5659   if (qualifierIndex!=-1)
5660   {
5661     explicitScopePart = name.left(qualifierIndex);
5662     replaceNamespaceAliases(explicitScopePart,explicitScopePart.length());
5663     name = name.mid(qualifierIndex+2);
5664   }
5665   //printf("explicitScopePart=%s\n",qPrint(explicitScopePart));
5666 
5667   int minDistance = 10000;
5668   MemberDef *bestMatch = 0;
5669 
5670   for (auto it=range.first; it!=range.second; ++it)
5671   {
5672     Definition *d = it->second;
5673     if (d->definitionType()==Definition::TypeMember)
5674     {
5675       SymbolResolver resolver(fileScope);
5676       int distance = resolver.isAccessibleFromWithExpScope(scope,d,explicitScopePart);
5677       if (distance!=-1 && distance<minDistance)
5678       {
5679         minDistance = distance;
5680         bestMatch = toMemberDef(d);
5681         //printf("new best match %s distance=%d\n",qPrint(bestMatch->qualifiedName()),distance);
5682       }
5683     }
5684   }
5685   return bestMatch;
5686 }
5687 
5688 /*! Returns true iff the given name string appears to be a typedef in scope. */
checkIfTypedef(const Definition * scope,const FileDef * fileScope,const QCString & n)5689 bool checkIfTypedef(const Definition *scope,const FileDef *fileScope,const QCString &n)
5690 {
5691   MemberDef *bestMatch = getMemberFromSymbol(scope,fileScope,n);
5692 
5693   if (bestMatch && bestMatch->isTypedef())
5694     return TRUE; // closest matching symbol is a typedef
5695   else
5696     return FALSE;
5697 }
5698 
nextUTF8CharPosition(const QCString & utf8Str,uint len,uint startPos)5699 static int nextUTF8CharPosition(const QCString &utf8Str,uint len,uint startPos)
5700 {
5701   if (startPos>=len) return len;
5702   uchar c = (uchar)utf8Str[startPos];
5703   int bytes=getUTF8CharNumBytes(c);
5704   if (c=='&') // skip over character entities
5705   {
5706     bytes=1;
5707     int (*matcher)(int) = 0;
5708     c = (uchar)utf8Str[startPos+bytes];
5709     if (c=='#') // numerical entity?
5710     {
5711       bytes++;
5712       c = (uchar)utf8Str[startPos+bytes];
5713       if (c=='x') // hexadecimal entity?
5714       {
5715         bytes++;
5716         matcher = std::isxdigit;
5717       }
5718       else // decimal entity
5719       {
5720         matcher = std::isdigit;
5721       }
5722     }
5723     else if (std::isalnum(c)) // named entity?
5724     {
5725       bytes++;
5726       matcher = std::isalnum;
5727     }
5728     if (matcher)
5729     {
5730       while ((c = (uchar)utf8Str[startPos+bytes])!=0 && matcher(c))
5731       {
5732         bytes++;
5733       }
5734     }
5735     if (c!=';')
5736     {
5737       bytes=1; // not a valid entity, reset bytes counter
5738     }
5739   }
5740   return startPos+bytes;
5741 }
5742 
parseCommentAsText(const Definition * scope,const MemberDef * md,const QCString & doc,const QCString & fileName,int lineNr)5743 QCString parseCommentAsText(const Definition *scope,const MemberDef *md,
5744     const QCString &doc,const QCString &fileName,int lineNr)
5745 {
5746   if (doc.isEmpty()) return "";
5747   //printf("parseCommentAsText(%s)\n",qPrint(doc));
5748   TextStream t;
5749   std::unique_ptr<IDocParser> parser { createDocParser() };
5750   std::unique_ptr<DocRoot>    root   { validatingParseDoc(*parser.get(),
5751                                        fileName,lineNr,
5752                                        (Definition*)scope,(MemberDef*)md,doc,FALSE,FALSE,
5753                                        QCString(),FALSE,FALSE,Config_getBool(MARKDOWN_SUPPORT)) };
5754   auto visitor = std::make_unique<TextDocVisitor>(t);
5755   root->accept(visitor.get());
5756   QCString result = convertCharEntitiesToUTF8(t.str().c_str()).stripWhiteSpace();
5757   int i=0;
5758   int charCnt=0;
5759   int l=result.length();
5760   while ((i=nextUTF8CharPosition(result,l,i))<l)
5761   {
5762     charCnt++;
5763     if (charCnt>=80) break;
5764   }
5765   if (charCnt>=80) // try to truncate the string
5766   {
5767     while ((i=nextUTF8CharPosition(result,l,i))<l && charCnt<100)
5768     {
5769       charCnt++;
5770       if (result.at(i)==',' ||
5771           result.at(i)=='.' ||
5772           result.at(i)=='!' ||
5773           result.at(i)=='?')
5774       {
5775         i++; // we want to be "behind" last inspected character
5776         break;
5777       }
5778     }
5779   }
5780   if ( i < l) result=result.left(i)+"...";
5781   return result.data();
5782 }
5783 
5784 //--------------------------------------------------------------------------------------
5785 
5786 static QCString expandAliasRec(StringUnorderedSet &aliasesProcessed,
5787                                const QCString &s,bool allowRecursion=FALSE);
5788 
5789 struct Marker
5790 {
MarkerMarker5791   Marker(int p, int n,int s) : pos(p),number(n),size(s) {}
5792   int pos; // position in the string
5793   int number; // argument number
5794   int size; // size of the marker
5795 };
5796 
5797 /** For a string \a s that starts with a command name, returns the character
5798  *  offset within that string representing the first character after the
5799  *  command. For an alias with argument, this is the offset to the
5800  *  character just after the argument list.
5801  *
5802  *  Examples:
5803  *  - s=="a b"      returns 1
5804  *  - s=="a{2,3} b" returns 6
5805  *  = s=="#"        returns 0
5806  */
findEndOfCommand(const char * s)5807 static int findEndOfCommand(const char *s)
5808 {
5809   const char *p = s;
5810   char c;
5811   int i=0;
5812   if (p)
5813   {
5814     while ((c=*p) && isId(c)) p++;
5815     if (c=='{')
5816     {
5817       QCString args = extractAliasArgs(p,0);
5818       i+=args.length();
5819     }
5820     i+=(int)(p-s);
5821   }
5822   return i;
5823 }
5824 
5825 /** Replaces the markers in an alias definition \a aliasValue
5826  *  with the corresponding values found in the comma separated argument
5827  *  list \a argList and the returns the result after recursive alias expansion.
5828  */
replaceAliasArguments(StringUnorderedSet & aliasesProcessed,const QCString & aliasValue,const QCString & argList)5829 static QCString replaceAliasArguments(StringUnorderedSet &aliasesProcessed,
5830                                       const QCString &aliasValue,const QCString &argList)
5831 {
5832   //printf("----- replaceAliasArguments(val=[%s],args=[%s])\n",qPrint(aliasValue),qPrint(argList));
5833 
5834   // first make a list of arguments from the comma separated argument list
5835   std::vector<QCString> args;
5836   int i,l=(int)argList.length();
5837   int s=0;
5838   for (i=0;i<l;i++)
5839   {
5840     char c = argList.at(i);
5841     if (c==',' && (i==0 || argList.at(i-1)!='\\'))
5842     {
5843       args.push_back(QCString(argList.mid(s,i-s)));
5844       s=i+1; // start of next argument
5845     }
5846     else if (c=='@' || c=='\\')
5847     {
5848       // check if this is the start of another aliased command (see bug704172)
5849       i+=findEndOfCommand(argList.data()+i+1);
5850     }
5851   }
5852   if (l>s) args.push_back(QCString(argList.right(l-s)));
5853   //printf("found %d arguments\n",args.count());
5854 
5855   // next we look for the positions of the markers and add them to a list
5856   std::vector<Marker> markerList;
5857   l = aliasValue.length();
5858   char pc='\0';
5859   bool insideMarkerId=false;
5860   int markerStart=0;
5861   auto isDigit = [](char c) { return c>='0' && c<='9'; };
5862   for (i=0;i<=l;i++)
5863   {
5864     char c = i<l ? aliasValue.at(i) : '\0';
5865     if (insideMarkerId && !isDigit(c)) // found end of a markerId
5866     {
5867       insideMarkerId = false;
5868       int markerLen = i-markerStart;
5869       markerList.push_back(Marker(markerStart-1,
5870                                   aliasValue.mid(markerStart,markerLen).toInt(),
5871                                   markerLen+1));
5872     }
5873     if (c=='\\' && (pc=='@' || pc=='\\')) // found escaped backslash
5874     {
5875       // skip
5876       pc = '\0';
5877     }
5878     else
5879     {
5880       if (isDigit(c) && pc=='\\') // found start of a markerId
5881       {
5882         insideMarkerId=true;
5883         markerStart=i;
5884       }
5885       pc = c;
5886     }
5887   }
5888 
5889   // then we replace the markers with the corresponding arguments in one pass
5890   QCString result;
5891   int p=0;
5892   for (i=0;i<(int)markerList.size();i++)
5893   {
5894     const Marker &m = markerList.at(i);
5895     result+=aliasValue.mid(p,m.pos-p);
5896     //printf("part before marker %d: '%s'\n",i,qPrint(aliasValue.mid(p,m->pos-p)));
5897     if (m.number>0 && m.number<=(int)args.size()) // valid number
5898     {
5899       result+=expandAliasRec(aliasesProcessed,args.at(m.number-1),TRUE);
5900       //printf("marker index=%d pos=%d number=%d size=%d replacement %s\n",i,m->pos,m->number,m->size,
5901       //    qPrint(args.at(m->number-1)));
5902     }
5903     p=m.pos+m.size; // continue after the marker
5904   }
5905   result+=aliasValue.right(l-p); // append remainder
5906   //printf("string after replacement of markers: '%s'\n",qPrint(result));
5907 
5908   // expand the result again
5909   result = substitute(result,"\\{","{");
5910   result = substitute(result,"\\}","}");
5911   result = expandAliasRec(aliasesProcessed,substitute(result,"\\,",","));
5912 
5913   return result;
5914 }
5915 
escapeCommas(const QCString & s)5916 static QCString escapeCommas(const QCString &s)
5917 {
5918   if (s.isEmpty()) return s;
5919   TextStream result;
5920   const char *p = s.data();
5921   char c,pc=0;
5922   while ((c=*p++))
5923   {
5924     if (c==',' && pc!='\\')
5925     {
5926       result << "\\,";
5927     }
5928     else
5929     {
5930       result << c;
5931     }
5932     pc=c;
5933   }
5934   //printf("escapeCommas: '%s'->'%s'\n",qPrint(s),qPrint(result));
5935   return result.str();
5936 }
5937 
expandAliasRec(StringUnorderedSet & aliasesProcessed,const QCString & s,bool allowRecursion)5938 static QCString expandAliasRec(StringUnorderedSet &aliasesProcessed,const QCString &s,bool allowRecursion)
5939 {
5940   QCString result;
5941   static const reg::Ex re(R"([\\@](\a\w*))");
5942   std::string str = s.str();
5943   reg::Match match;
5944   size_t p = 0;
5945   while (search(str,match,re,p))
5946   {
5947     size_t i = match.position();
5948     size_t l = match.length();
5949     if (i>p) result+=s.mid(p,i-p);
5950 
5951     QCString args = extractAliasArgs(s,i+l);
5952     bool hasArgs = !args.isEmpty();            // found directly after command
5953     int argsLen = args.length();
5954     QCString cmd = match[1].str();
5955     QCString cmdNoArgs = cmd;
5956     int numArgs=0;
5957     if (hasArgs)
5958     {
5959       numArgs = countAliasArguments(args);
5960       cmd += QCString().sprintf("{%d}",numArgs);  // alias name + {n}
5961     }
5962     auto it = Doxygen::aliasMap.find(cmd.str());
5963     if (numArgs>1 && it==Doxygen::aliasMap.end())
5964     { // in case there is no command with numArgs parameters, but there is a command with 1 parameter,
5965       // we also accept all text as the argument of that command (so you don't have to escape commas)
5966       it = Doxygen::aliasMap.find((cmdNoArgs+"{1}").str());
5967       if (it!=Doxygen::aliasMap.end())
5968       {
5969         cmd = cmdNoArgs+"{1}";
5970         args = escapeCommas(args); // escape , so that everything is seen as one argument
5971       }
5972     }
5973     //printf("Found command s='%s' cmd='%s' numArgs=%d args='%s' aliasText=%s\n",
5974     //    s.data(),cmd.data(),numArgs,args.data(),aliasText?aliasText->data():"<none>");
5975     if ((allowRecursion || aliasesProcessed.find(cmd.str())==aliasesProcessed.end()) &&
5976         it!=Doxygen::aliasMap.end()) // expand the alias
5977     {
5978       //printf("is an alias!\n");
5979       if (!allowRecursion) aliasesProcessed.insert(cmd.str());
5980       QCString val(it->second);
5981       if (hasArgs)
5982       {
5983         val = replaceAliasArguments(aliasesProcessed,val,args);
5984         //printf("replace '%s'->'%s' args='%s'\n",
5985         //       aliasText->data(),val.data(),args.data());
5986       }
5987       result+=expandAliasRec(aliasesProcessed,val);
5988       if (!allowRecursion) aliasesProcessed.erase(cmd.str());
5989       p=i+l;
5990       if (hasArgs) p+=argsLen+2;
5991     }
5992     else // command is not an alias
5993     {
5994       //printf("not an alias!\n");
5995       result+=match.str();
5996       p=i+l;
5997     }
5998   }
5999   result+=s.right(s.length()-p);
6000 
6001   //printf("expandAliases '%s'->'%s'\n",s.data(),result.data());
6002   return result;
6003 }
6004 
6005 
countAliasArguments(const QCString & argList)6006 int countAliasArguments(const QCString &argList)
6007 {
6008   int count=1;
6009   int l = argList.length();
6010   int i;
6011   for (i=0;i<l;i++)
6012   {
6013     char c = argList.at(i);
6014     if (c==',' && (i==0 || argList.at(i-1)!='\\')) count++;
6015     else if (c=='@' || c=='\\')
6016     {
6017       // check if this is the start of another aliased command (see bug704172)
6018       i+=findEndOfCommand(argList.data()+i+1);
6019     }
6020   }
6021   //printf("countAliasArguments=%d\n",count);
6022   return count;
6023 }
6024 
extractAliasArgs(const QCString & args,size_t pos)6025 QCString extractAliasArgs(const QCString &args,size_t pos)
6026 {
6027   size_t i;
6028   int bc=0;
6029   char prevChar=0;
6030   if (args.at(pos)=='{') // alias has argument
6031   {
6032     for (i=pos;i<args.length();i++)
6033     {
6034       if (prevChar!='\\')
6035       {
6036         if (args.at(i)=='{') bc++;
6037         if (args.at(i)=='}') bc--;
6038         prevChar=args.at(i);
6039       }
6040       else
6041       {
6042         prevChar=0;
6043       }
6044 
6045       if (bc==0)
6046       {
6047         //printf("extractAliasArgs('%s')->'%s'\n",qPrint(args),qPrint(args.mid(pos+1,i-pos-1)));
6048         return args.mid(pos+1,i-pos-1);
6049       }
6050     }
6051   }
6052   return "";
6053 }
6054 
resolveAliasCmd(const QCString & aliasCmd)6055 QCString resolveAliasCmd(const QCString &aliasCmd)
6056 {
6057   QCString result;
6058   StringUnorderedSet aliasesProcessed;
6059   //printf("Expanding: '%s'\n",qPrint(aliasCmd));
6060   result = expandAliasRec(aliasesProcessed,aliasCmd);
6061   //printf("Expanding result: '%s'->'%s'\n",qPrint(aliasCmd),qPrint(result));
6062   return result;
6063 }
6064 
expandAlias(const std::string & aliasName,const std::string & aliasValue)6065 std::string expandAlias(const std::string &aliasName,const std::string &aliasValue)
6066 {
6067   QCString result;
6068   StringUnorderedSet aliasesProcessed;
6069   // avoid expanding this command recursively
6070   aliasesProcessed.insert(aliasName);
6071   // expand embedded commands
6072   //printf("Expanding: '%s'->'%s'\n",qPrint(aliasName),qPrint(aliasValue));
6073   result = expandAliasRec(aliasesProcessed,aliasValue.c_str());
6074   //printf("Expanding result: '%s'->'%s'\n",qPrint(aliasName),qPrint(result));
6075   return result.str();
6076 }
6077 
writeTypeConstraints(OutputList & ol,const Definition * d,const ArgumentList & al)6078 void writeTypeConstraints(OutputList &ol,const Definition *d,const ArgumentList &al)
6079 {
6080   if (al.empty()) return;
6081   ol.startConstraintList(theTranslator->trTypeConstraints());
6082   for (const Argument &a : al)
6083   {
6084     ol.startConstraintParam();
6085     ol.parseText(a.name);
6086     ol.endConstraintParam();
6087     ol.startConstraintType();
6088     linkifyText(TextGeneratorOLImpl(ol),d,0,0,a.type);
6089     ol.endConstraintType();
6090     ol.startConstraintDocs();
6091     ol.generateDoc(d->docFile(),d->docLine(),d,0,a.docs,TRUE,FALSE,
6092                    QCString(),FALSE,FALSE,Config_getBool(MARKDOWN_SUPPORT));
6093     ol.endConstraintDocs();
6094   }
6095   ol.endConstraintList();
6096 }
6097 
6098 //----------------------------------------------------------------------------
6099 
stackTrace()6100 void stackTrace()
6101 {
6102 #ifdef TRACINGSUPPORT
6103   void *backtraceFrames[128];
6104   int frameCount = backtrace(backtraceFrames, 128);
6105   static char cmd[40960];
6106   char *p = cmd;
6107   p += sprintf(p,"/usr/bin/atos -p %d ", (int)getpid());
6108   for (int x = 0; x < frameCount; x++)
6109   {
6110     p += sprintf(p,"%p ", backtraceFrames[x]);
6111   }
6112   fprintf(stderr,"========== STACKTRACE START ==============\n");
6113   if (FILE *fp = Portable::popen(cmd, "r"))
6114   {
6115     char resBuf[512];
6116     while (size_t len = fread(resBuf, 1, sizeof(resBuf), fp))
6117     {
6118       fwrite(resBuf, 1, len, stderr);
6119     }
6120     Portable::pclose(fp);
6121   }
6122   fprintf(stderr,"============ STACKTRACE END ==============\n");
6123   //fprintf(stderr,"%s\n", frameStrings[x]);
6124 #endif
6125 }
6126 
transcodeCharacterBuffer(const QCString & fileName,BufStr & srcBuf,int size,const QCString & inputEncoding,const QCString & outputEncoding)6127 static int transcodeCharacterBuffer(const QCString &fileName,BufStr &srcBuf,int size,
6128            const QCString &inputEncoding,const QCString &outputEncoding)
6129 {
6130   if (inputEncoding.isEmpty() || outputEncoding.isEmpty()) return size;
6131   if (qstricmp(inputEncoding,outputEncoding)==0) return size;
6132   void *cd = portable_iconv_open(outputEncoding.data(),inputEncoding.data());
6133   if (cd==(void *)(-1))
6134   {
6135     term("unsupported character conversion: '%s'->'%s': %s\n"
6136         "Check the INPUT_ENCODING setting in the config file!\n",
6137         qPrint(inputEncoding),qPrint(outputEncoding),strerror(errno));
6138   }
6139   int tmpBufSize=size*4+1;
6140   BufStr tmpBuf(tmpBufSize);
6141   size_t iLeft=size;
6142   size_t oLeft=tmpBufSize;
6143   const char *srcPtr = srcBuf.data();
6144   char *dstPtr = tmpBuf.data();
6145   uint newSize=0;
6146   if (!portable_iconv(cd, &srcPtr, &iLeft, &dstPtr, &oLeft))
6147   {
6148     newSize = tmpBufSize-(int)oLeft;
6149     srcBuf.shrink(newSize);
6150     strncpy(srcBuf.data(),tmpBuf.data(),newSize);
6151     //printf("iconv: input size=%d output size=%d\n[%s]\n",size,newSize,qPrint(srcBuf));
6152   }
6153   else
6154   {
6155     term("%s: failed to translate characters from %s to %s: check INPUT_ENCODING\n",
6156         qPrint(fileName),qPrint(inputEncoding),qPrint(outputEncoding));
6157   }
6158   portable_iconv_close(cd);
6159   return newSize;
6160 }
6161 
6162 //! read a file name \a fileName and optionally filter and transcode it
readInputFile(const QCString & fileName,BufStr & inBuf,bool filter,bool isSourceCode)6163 bool readInputFile(const QCString &fileName,BufStr &inBuf,bool filter,bool isSourceCode)
6164 {
6165   // try to open file
6166   int size=0;
6167 
6168   FileInfo fi(fileName.str());
6169   if (!fi.exists()) return FALSE;
6170   QCString filterName = getFileFilter(fileName,isSourceCode);
6171   if (filterName.isEmpty() || !filter)
6172   {
6173     std::ifstream f(fileName.str(),std::ifstream::in | std::ifstream::binary);
6174     if (!f.is_open())
6175     {
6176       err("could not open file %s\n",qPrint(fileName));
6177       return FALSE;
6178     }
6179     size=(int)fi.size();
6180     // read the file
6181     inBuf.skip(size);
6182     f.read(inBuf.data(),size);
6183     if (f.fail())
6184     {
6185       err("problems while reading file %s\n",qPrint(fileName));
6186       return FALSE;
6187     }
6188   }
6189   else
6190   {
6191     QCString cmd=filterName+" \""+fileName+"\"";
6192     Debug::print(Debug::ExtCmd,0,"Executing popen(`%s`)\n",qPrint(cmd));
6193     FILE *f=Portable::popen(cmd,"r");
6194     if (!f)
6195     {
6196       err("could not execute filter %s\n",qPrint(filterName));
6197       return FALSE;
6198     }
6199     const int bufSize=1024;
6200     char buf[bufSize];
6201     int numRead;
6202     while ((numRead=(int)fread(buf,1,bufSize,f))>0)
6203     {
6204       //printf(">>>>>>>>Reading %d bytes\n",numRead);
6205       inBuf.addArray(buf,numRead),size+=numRead;
6206     }
6207     Portable::pclose(f);
6208     inBuf.at(inBuf.curPos()) ='\0';
6209     Debug::print(Debug::FilterOutput, 0, "Filter output\n");
6210     Debug::print(Debug::FilterOutput,0,"-------------\n%s\n-------------\n",qPrint(inBuf));
6211   }
6212 
6213   int start=0;
6214   if (size>=2 &&
6215       ((uchar)inBuf.at(0)==0xFF && (uchar)inBuf.at(1)==0xFE) // Little endian BOM
6216      ) // UCS-2LE encoded file
6217   {
6218     transcodeCharacterBuffer(fileName,inBuf,inBuf.curPos(),
6219         "UCS-2LE","UTF-8");
6220   }
6221   else if (size>=2 &&
6222            ((uchar)inBuf.at(0)==0xFE && (uchar)inBuf.at(1)==0xFF) // big endian BOM
6223          ) // UCS-2BE encoded file
6224   {
6225     transcodeCharacterBuffer(fileName,inBuf,inBuf.curPos(),
6226         "UCS-2BE","UTF-8");
6227   }
6228   else if (size>=3 &&
6229            (uchar)inBuf.at(0)==0xEF &&
6230            (uchar)inBuf.at(1)==0xBB &&
6231            (uchar)inBuf.at(2)==0xBF
6232      ) // UTF-8 encoded file
6233   {
6234     inBuf.dropFromStart(3); // remove UTF-8 BOM: no translation needed
6235   }
6236   else // transcode according to the INPUT_ENCODING setting
6237   {
6238     // do character transcoding if needed.
6239     transcodeCharacterBuffer(fileName,inBuf,inBuf.curPos(),
6240         Config_getString(INPUT_ENCODING),"UTF-8");
6241   }
6242 
6243   //inBuf.addChar('\n'); /* to prevent problems under Windows ? */
6244 
6245   // and translate CR's
6246   size=inBuf.curPos()-start;
6247   int newSize=filterCRLF(inBuf.data()+start,size);
6248   //printf("filter char at %p size=%d newSize=%d\n",qPrint(dest)+oldPos,size,newSize);
6249   if (newSize!=size) // we removed chars
6250   {
6251     inBuf.shrink(newSize); // resize the array
6252     //printf(".......resizing from %d to %d result=[%s]\n",oldPos+size,oldPos+newSize,qPrint(dest));
6253   }
6254   inBuf.addChar(0);
6255   return TRUE;
6256 }
6257 
6258 // Replace %word by word in title
filterTitle(const QCString & title)6259 QCString filterTitle(const QCString &title)
6260 {
6261   std::string tf;
6262   std::string t = title.str();
6263   static const reg::Ex re(R"(%[a-z_A-Z]+)");
6264   reg::Iterator it(t,re);
6265   reg::Iterator end;
6266   size_t p = 0;
6267   for (; it!=end ; ++it)
6268   {
6269     const auto &match = *it;
6270     size_t i = match.position();
6271     size_t l = match.length();
6272     if (i>p) tf+=t.substr(p,i-p);
6273     tf+=match.str().substr(1); // skip %
6274     p=i+l;
6275   }
6276   tf+=t.substr(p);
6277   return QCString(tf);
6278 }
6279 
6280 //----------------------------------------------------------------------------
6281 // returns TRUE if the name of the file represented by 'fi' matches
6282 // one of the file patterns in the 'patList' list.
6283 
patternMatch(const FileInfo & fi,const StringVector & patList)6284 bool patternMatch(const FileInfo &fi,const StringVector &patList)
6285 {
6286   bool caseSenseNames = Config_getBool(CASE_SENSE_NAMES);
6287   bool found = FALSE;
6288 
6289   // For platforms where the file system is non case sensitive overrule the setting
6290   if (!Portable::fileSystemIsCaseSensitive())
6291   {
6292     caseSenseNames = FALSE;
6293   }
6294 
6295   if (!patList.empty())
6296   {
6297     std::string fn = fi.fileName();
6298     std::string fp = fi.filePath();
6299     std::string afp= fi.absFilePath();
6300 
6301     for (auto pattern: patList)
6302     {
6303       if (!pattern.empty())
6304       {
6305         size_t i=pattern.find('=');
6306         if (i!=std::string::npos) pattern=pattern.substr(0,i); // strip of the extension specific filter name
6307 
6308         if (!caseSenseNames)
6309         {
6310           pattern = QCString(pattern).lower().str();
6311           fn      = QCString(fn).lower().str();
6312           fp      = QCString(fp).lower().str();
6313           afp     = QCString(afp).lower().str();
6314         }
6315         reg::Ex re(pattern,reg::Ex::Mode::Wildcard);
6316         found = re.isValid() && (reg::match(fn,re) ||
6317                                  (fn!=fp && reg::match(fp,re)) ||
6318                                  (fn!=afp && fp!=afp && reg::match(afp,re)));
6319         if (found) break;
6320         //printf("Matching '%s' against pattern '%s' found=%d\n",
6321         //    qPrint(fi->fileName()),qPrint(pattern),found);
6322       }
6323     }
6324   }
6325   return found;
6326 }
6327 
externalLinkTarget(const bool parent)6328 QCString externalLinkTarget(const bool parent)
6329 {
6330   static bool extLinksInWindow = Config_getBool(EXT_LINKS_IN_WINDOW);
6331   if (extLinksInWindow)
6332     return "target=\"_blank\" ";
6333   else if (parent)
6334     return "target=\"_parent\" ";
6335   else
6336     return "";
6337 }
6338 
externalRef(const QCString & relPath,const QCString & ref,bool href)6339 QCString externalRef(const QCString &relPath,const QCString &ref,bool href)
6340 {
6341   QCString result;
6342   if (!ref.isEmpty())
6343   {
6344     auto it = Doxygen::tagDestinationMap.find(ref.str());
6345     if (it!=Doxygen::tagDestinationMap.end())
6346     {
6347       result = it->second;
6348       int l = result.length();
6349       if (!relPath.isEmpty() && l>0 && result.at(0)=='.')
6350       { // relative path -> prepend relPath.
6351         result.prepend(relPath);
6352         l+=relPath.length();
6353       }
6354       if (l>0 && result.at(l-1)!='/') result+='/';
6355       if (!href) result.append("\" ");
6356     }
6357   }
6358   else
6359   {
6360     result = relPath;
6361   }
6362   return result;
6363 }
6364 
6365 /** Writes the intensity only bitmap represented by \a data as an image to
6366  *  directory \a dir using the colors defined by HTML_COLORSTYLE_*.
6367  */
writeColoredImgData(const QCString & dir,ColoredImgDataItem data[])6368 void writeColoredImgData(const QCString &dir,ColoredImgDataItem data[])
6369 {
6370   static int hue   = Config_getInt(HTML_COLORSTYLE_HUE);
6371   static int sat   = Config_getInt(HTML_COLORSTYLE_SAT);
6372   static int gamma = Config_getInt(HTML_COLORSTYLE_GAMMA);
6373   while (data->name)
6374   {
6375     QCString fileName = dir+"/"+data->name;
6376     ColoredImage img(data->width,data->height,data->content,data->alpha,
6377                      sat,hue,gamma);
6378     if (!img.save(fileName))
6379     {
6380       fprintf(stderr,"Warning: Cannot open file %s for writing\n",data->name);
6381     }
6382     Doxygen::indexList->addImageFile(data->name);
6383     data++;
6384   }
6385 }
6386 
6387 /** Replaces any markers of the form \#\#AA in input string \a str
6388  *  by new markers of the form \#AABBCC, where \#AABBCC represents a
6389  *  valid color, based on the intensity represented by hex number AA
6390  *  and the current HTML_COLORSTYLE_* settings.
6391  */
replaceColorMarkers(const QCString & str)6392 QCString replaceColorMarkers(const QCString &str)
6393 {
6394   if (str.isEmpty()) return QCString();
6395   std::string result;
6396   std::string s=str.str();
6397   static const reg::Ex re(R"(##[0-9A-Fa-f][0-9A-Fa-f])");
6398   reg::Iterator it(s,re);
6399   reg::Iterator end;
6400   static int hue   = Config_getInt(HTML_COLORSTYLE_HUE);
6401   static int sat   = Config_getInt(HTML_COLORSTYLE_SAT);
6402   static int gamma = Config_getInt(HTML_COLORSTYLE_GAMMA);
6403   size_t sl=s.length();
6404   size_t p=0;
6405   for (; it!=end ; ++it)
6406   {
6407     const auto &match = *it;
6408     size_t i = match.position();
6409     size_t l = match.length();
6410     if (i>p) result+=s.substr(p,i-p);
6411     std::string lumStr = match.str().substr(2);
6412 #define HEXTONUM(x) (((x)>='0' && (x)<='9') ? ((x)-'0') :       \
6413                      ((x)>='a' && (x)<='f') ? ((x)-'a'+10) :    \
6414                      ((x)>='A' && (x)<='F') ? ((x)-'A'+10) : 0)
6415 
6416     double r,g,b;
6417     int red,green,blue;
6418     int level = HEXTONUM(lumStr[0])*16+HEXTONUM(lumStr[1]);
6419     ColoredImage::hsl2rgb(hue/360.0,sat/255.0,
6420                           pow(level/255.0,gamma/100.0),&r,&g,&b);
6421     red   = (int)(r*255.0);
6422     green = (int)(g*255.0);
6423     blue  = (int)(b*255.0);
6424     char colStr[8];
6425     colStr[0]='#';
6426     colStr[1]=hex[red>>4];
6427     colStr[2]=hex[red&0xf];
6428     colStr[3]=hex[green>>4];
6429     colStr[4]=hex[green&0xf];
6430     colStr[5]=hex[blue>>4];
6431     colStr[6]=hex[blue&0xf];
6432     colStr[7]=0;
6433     //printf("replacing %s->%s (level=%d)\n",qPrint(lumStr),colStr,level);
6434     result+=colStr;
6435     p=i+l;
6436   }
6437   if (p<sl) result+=s.substr(p);
6438   return QCString(result);
6439 }
6440 
6441 /** Copies the contents of file with name \a src to the newly created
6442  *  file with name \a dest. Returns TRUE if successful.
6443  */
copyFile(const QCString & src,const QCString & dest)6444 bool copyFile(const QCString &src,const QCString &dest)
6445 {
6446   if (!Dir().copy(src.str(),dest.str()))
6447   {
6448     err("could not copy file %s to %s\n",qPrint(src),qPrint(dest));
6449     return false;
6450   }
6451   return true;
6452 }
6453 
6454 /** Returns the section of text, in between a pair of markers.
6455  *  Full lines are returned, excluding the lines on which the markers appear.
6456  *  \sa routine lineBlock
6457  */
extractBlock(const QCString & text,const QCString & marker)6458 QCString extractBlock(const QCString &text,const QCString &marker)
6459 {
6460   QCString result;
6461   int p=0,i;
6462   bool found=FALSE;
6463 
6464   // find the character positions of the markers
6465   int m1 = text.find(marker);
6466   if (m1==-1) return result;
6467   int m2 = text.find(marker,m1+marker.length());
6468   if (m2==-1) return result;
6469 
6470   // find start and end line positions for the markers
6471   int l1=-1,l2=-1;
6472   while (!found && (i=text.find('\n',p))!=-1)
6473   {
6474     found = (p<=m1 && m1<i); // found the line with the start marker
6475     p=i+1;
6476   }
6477   l1=p;
6478   int lp=i;
6479   if (found)
6480   {
6481     while ((i=text.find('\n',p))!=-1)
6482     {
6483       if (p<=m2 && m2<i) // found the line with the end marker
6484       {
6485         l2=p;
6486         break;
6487       }
6488       p=i+1;
6489       lp=i;
6490     }
6491   }
6492   if (l2==-1) // marker at last line without newline (see bug706874)
6493   {
6494     l2=lp;
6495   }
6496   //printf("text=[%s]\n",qPrint(text.mid(l1,l2-l1)));
6497   return l2>l1 ? text.mid(l1,l2-l1) : QCString();
6498 }
6499 
6500 /** Returns the line number of the line following the line with the marker.
6501  *  \sa routine extractBlock
6502  */
lineBlock(const QCString & text,const QCString & marker)6503 int lineBlock(const QCString &text,const QCString &marker)
6504 {
6505   int result = 1;
6506   int p=0,i;
6507   bool found=FALSE;
6508 
6509   // find the character positions of the first marker
6510   int m1 = text.find(marker);
6511   if (m1==-1) return result;
6512 
6513   // find start line positions for the markers
6514   while (!found && (i=text.find('\n',p))!=-1)
6515   {
6516     found = (p<=m1 && m1<i); // found the line with the start marker
6517     p=i+1;
6518     result++;
6519   }
6520   return result;
6521 }
6522 
6523 /** Returns a string representation of \a lang. */
langToString(SrcLangExt lang)6524 QCString langToString(SrcLangExt lang)
6525 {
6526   switch(lang)
6527   {
6528     case SrcLangExt_Unknown:  return "Unknown";
6529     case SrcLangExt_IDL:      return "IDL";
6530     case SrcLangExt_Java:     return "Java";
6531     case SrcLangExt_CSharp:   return "C#";
6532     case SrcLangExt_D:        return "D";
6533     case SrcLangExt_PHP:      return "PHP";
6534     case SrcLangExt_ObjC:     return "Objective-C";
6535     case SrcLangExt_Cpp:      return "C++";
6536     case SrcLangExt_JS:       return "JavaScript";
6537     case SrcLangExt_Python:   return "Python";
6538     case SrcLangExt_Fortran:  return "Fortran";
6539     case SrcLangExt_VHDL:     return "VHDL";
6540     case SrcLangExt_XML:      return "XML";
6541     case SrcLangExt_SQL:      return "SQL";
6542     case SrcLangExt_Markdown: return "Markdown";
6543     case SrcLangExt_Slice:    return "Slice";
6544     case SrcLangExt_Lex:      return "Lex";
6545   }
6546   return "Unknown";
6547 }
6548 
6549 /** Returns the scope separator to use given the programming language \a lang */
getLanguageSpecificSeparator(SrcLangExt lang,bool classScope)6550 QCString getLanguageSpecificSeparator(SrcLangExt lang,bool classScope)
6551 {
6552   if (lang==SrcLangExt_Java || lang==SrcLangExt_CSharp || lang==SrcLangExt_VHDL || lang==SrcLangExt_Python)
6553   {
6554     return ".";
6555   }
6556   else if (lang==SrcLangExt_PHP && !classScope)
6557   {
6558     return "\\";
6559   }
6560   else
6561   {
6562     return "::";
6563   }
6564 }
6565 /** Checks whether the given url starts with a supported protocol */
isURL(const QCString & url)6566 bool isURL(const QCString &url)
6567 {
6568   QCString loc_url = url.stripWhiteSpace();
6569   return loc_url.left(5)=="http:" || loc_url.left(6)=="https:" ||
6570          loc_url.left(4)=="ftp:"  || loc_url.left(5)=="ftps:"  ||
6571          loc_url.left(5)=="sftp:" || loc_url.left(5)=="file:"  ||
6572          loc_url.left(5)=="news:" || loc_url.left(4)=="irc:"   ||
6573          loc_url.left(5)=="ircs:";
6574 }
6575 /** Corrects URL \a url according to the relative path \a relPath.
6576  *  Returns the corrected URL. For absolute URLs no correction will be done.
6577  */
correctURL(const QCString & url,const QCString & relPath)6578 QCString correctURL(const QCString &url,const QCString &relPath)
6579 {
6580   QCString result = url;
6581   if (!relPath.isEmpty() && !isURL(url))
6582   {
6583     result.prepend(relPath);
6584   }
6585   return result;
6586 }
6587 
6588 //---------------------------------------------------------------------------
6589 
protectionLevelVisible(Protection prot)6590 bool protectionLevelVisible(Protection prot)
6591 {
6592   static bool extractPrivate = Config_getBool(EXTRACT_PRIVATE);
6593   static bool extractPackage = Config_getBool(EXTRACT_PACKAGE);
6594 
6595   return (prot!=Private && prot!=Package)  ||
6596          (prot==Private && extractPrivate) ||
6597          (prot==Package && extractPackage);
6598 }
6599 
6600 //---------------------------------------------------------------------------
6601 
stripIndentation(const QCString & s)6602 QCString stripIndentation(const QCString &s)
6603 {
6604   if (s.isEmpty()) return s; // empty string -> we're done
6605 
6606   //printf("stripIndentation:\n%s\n------\n",qPrint(s));
6607   // compute minimum indentation over all lines
6608   const char *p=s.data();
6609   char c;
6610   int indent=0;
6611   int minIndent=1000000; // "infinite"
6612   bool searchIndent=TRUE;
6613   static int tabSize=Config_getInt(TAB_SIZE);
6614   while ((c=*p++))
6615   {
6616     if      (c=='\t') indent+=tabSize - (indent%tabSize);
6617     else if (c=='\n') indent=0,searchIndent=TRUE;
6618     else if (c==' ')  indent++;
6619     else if (searchIndent)
6620     {
6621       searchIndent=FALSE;
6622       if (indent<minIndent) minIndent=indent;
6623     }
6624   }
6625 
6626   // no indent to remove -> we're done
6627   if (minIndent==0) return s;
6628 
6629   // remove minimum indentation for each line
6630   TextStream result;
6631   p=s.data();
6632   indent=0;
6633   while ((c=*p++))
6634   {
6635     if (c=='\n') // start of new line
6636     {
6637       indent=0;
6638       result << c;
6639     }
6640     else if (indent<minIndent) // skip until we reach minIndent
6641     {
6642       if (c=='\t')
6643       {
6644         int newIndent = indent+tabSize-(indent%tabSize);
6645         int i=newIndent;
6646         while (i>minIndent) // if a tab crosses the minIndent boundary fill the rest with spaces
6647         {
6648           result << ' ';
6649           i--;
6650         }
6651         indent=newIndent;
6652       }
6653       else // space
6654       {
6655         indent++;
6656       }
6657     }
6658     else // copy anything until the end of the line
6659     {
6660       result << c;
6661     }
6662   }
6663 
6664   return result.str();
6665 }
6666 
6667 // strip up to \a indentationLevel spaces from each line in \a doc (excluding the first line)
stripIndentation(QCString & doc,const int indentationLevel)6668 void stripIndentation(QCString &doc,const int indentationLevel)
6669 {
6670   if (indentationLevel <= 0 || doc.isEmpty()) return; // nothing to strip
6671 
6672   // by stripping content the string will only become shorter so we write the results
6673   // back into the input string and then resize it at the end.
6674   char c;
6675   const char *src = doc.data();
6676   char *dst = doc.rawData();
6677   bool insideIndent = false; // skip the initial line from stripping
6678   int cnt = 0;
6679   while ((c=*src++)!=0)
6680   {
6681     // invariant: dst<=src
6682     switch(c)
6683     {
6684       case '\n':
6685         *dst++ = c;
6686         insideIndent = true;
6687         cnt = indentationLevel;
6688         break;
6689       case ' ':
6690         if (insideIndent)
6691         {
6692           if (cnt>0) // count down the spacing until the end of the indent
6693           {
6694             cnt--;
6695           }
6696           else // reached the end of the indent, start of the part of the line to keep
6697           {
6698             insideIndent = false;
6699             *dst++ = c;
6700           }
6701         }
6702         else // part after indent, copy to the output
6703         {
6704           *dst++ = c;
6705         }
6706         break;
6707       default:
6708         insideIndent = false;
6709         *dst++ = c;
6710         break;
6711     }
6712   }
6713   doc.resize(static_cast<uint>(dst-doc.data())+1);
6714 }
6715 
6716 
fileVisibleInIndex(const FileDef * fd,bool & genSourceFile)6717 bool fileVisibleInIndex(const FileDef *fd,bool &genSourceFile)
6718 {
6719   static bool allExternals = Config_getBool(ALLEXTERNALS);
6720   bool isDocFile = fd->isDocumentationFile();
6721   genSourceFile = !isDocFile && fd->generateSourceFile();
6722   return ( ((allExternals && fd->isLinkable()) ||
6723             fd->isLinkableInProject()
6724            ) &&
6725            !isDocFile
6726          );
6727 }
6728 
6729 //--------------------------------------------------------------------------------------
6730 
6731 #if 0
6732 /*! @brief Get one unicode character as an unsigned integer from utf-8 string
6733  *
6734  * @param s utf-8 encoded string
6735  * @param idx byte position of given string \a s.
6736  * @return the unicode codepoint, 0 - MAX_UNICODE_CODEPOINT
6737  * @see getNextUtf8OrToLower()
6738  * @see getNextUtf8OrToUpper()
6739  */
6740 uint getUtf8Code( const QCString& s, int idx )
6741 {
6742   const int length = s.length();
6743   if (idx >= length) { return 0; }
6744   const uint c0 = (uchar)s.at(idx);
6745   if ( c0 < 0xC2 || c0 >= 0xF8 ) // 1 byte character
6746   {
6747     return c0;
6748   }
6749   if (idx+1 >= length) { return 0; }
6750   const uint c1 = ((uchar)s.at(idx+1)) & 0x3f;
6751   if ( c0 < 0xE0 ) // 2 byte character
6752   {
6753     return ((c0 & 0x1f) << 6) | c1;
6754   }
6755   if (idx+2 >= length) { return 0; }
6756   const uint c2 = ((uchar)s.at(idx+2)) & 0x3f;
6757   if ( c0 < 0xF0 ) // 3 byte character
6758   {
6759     return ((c0 & 0x0f) << 12) | (c1 << 6) | c2;
6760   }
6761   if (idx+3 >= length) { return 0; }
6762   // 4 byte character
6763   const uint c3 = ((uchar)s.at(idx+3)) & 0x3f;
6764   return ((c0 & 0x07) << 18) | (c1 << 12) | (c2 << 6) | c3;
6765 }
6766 
6767 
6768 /*! @brief Returns one unicode character as an unsigned integer
6769  *  from utf-8 string, making the character lower case if it was upper case.
6770  *
6771  * @param s utf-8 encoded string
6772  * @param idx byte position of given string \a s.
6773  * @return the unicode codepoint, 0 - MAX_UNICODE_CODEPOINT, excludes 'A'-'Z'
6774  * @see getNextUtf8Code()
6775 */
6776 uint getUtf8CodeToLower( const QCString& s, int idx )
6777 {
6778   const uint v = getUtf8Code( s, idx );
6779   return v < 0x7f ? tolower( v ) : v;
6780 }
6781 
6782 
6783 /*! @brief Returns one unicode character as an unsigned integer
6784  *  from utf-8 string, making the character upper case if it was lower case.
6785  *
6786  * @param s utf-8 encoded string
6787  * @param idx byte position of given string \a s.
6788  * @return the unicode codepoint, 0 - MAX_UNICODE_CODEPOINT, excludes 'A'-'Z'
6789  * @see getNextUtf8Code()
6790  */
6791 uint getUtf8CodeToUpper( const QCString& s, int idx )
6792 {
6793   const uint v = getUtf8Code( s, idx );
6794   return v < 0x7f ? toupper( v ) : v;
6795 }
6796 #endif
6797 
6798 
6799 
6800 //----------------------------------------------------------------------------
6801 
6802 /** Strip the direction part from docs and return it as a string in canonical form
6803  *  The input \a docs string can start with e.g. "[in]", "[in, out]", "[inout]", "[out,in]"...
6804  *  @returns either "[in,out]", "[in]", or "[out]" or the empty string.
6805  */
extractDirection(QCString & docs)6806 QCString extractDirection(QCString &docs)
6807 {
6808   std::string s = docs.str();
6809   static const reg::Ex re(R"(\[([ inout,]+)\])");
6810   reg::Iterator it(s,re);
6811   reg::Iterator end;
6812   if (it!=end)
6813   {
6814     const auto &match = *it;
6815     size_t p = match.position();
6816     size_t l = match.length();
6817     if (p==0 && l>2)
6818     {
6819       // make dir the part inside [...] without separators
6820       std::string dir = match[1].str();
6821       // strip , and ' ' from dir
6822       dir.erase(std::remove_if(dir.begin(),dir.end(),
6823                                [](const char c) { return c==' ' || c==','; }
6824                               ),dir.end());
6825       size_t inIndex, outIndex;
6826       unsigned char ioMask=0;
6827       if (( inIndex=dir.find( "in"))!=std::string::npos) dir.erase( inIndex,2),ioMask|=(1<<0);
6828       if ((outIndex=dir.find("out"))!=std::string::npos) dir.erase(outIndex,3),ioMask|=(1<<1);
6829       if (dir.empty() && ioMask!=0) // only in and/or out attributes found
6830       {
6831         docs = s.substr(l); // strip attributes
6832         if (ioMask==((1<<0)|(1<<1))) return "[in,out]";
6833         else if (ioMask==(1<<0))     return "[in]";
6834         else if (ioMask==(1<<1))     return "[out]";
6835       }
6836     }
6837   }
6838   return "";
6839 }
6840 
6841 //-----------------------------------------------------------
6842 
6843 /** Computes for a given list type \a inListType, which are the
6844  *  the corresponding list type(s) in the base class that are to be
6845  *  added to this list.
6846  *
6847  *  So for public inheritance, the mapping is 1-1, so outListType1=inListType
6848  *  Private members are to be hidden completely.
6849  *
6850  *  For protected inheritance, both protected and public members of the
6851  *  base class should be joined in the protected member section.
6852  *
6853  *  For private inheritance, both protected and public members of the
6854  *  base class should be joined in the private member section.
6855  */
convertProtectionLevel(MemberListType inListType,Protection inProt,int * outListType1,int * outListType2)6856 void convertProtectionLevel(
6857                    MemberListType inListType,
6858                    Protection inProt,
6859                    int *outListType1,
6860                    int *outListType2
6861                   )
6862 {
6863   static bool extractPrivate = Config_getBool(EXTRACT_PRIVATE);
6864   // default representing 1-1 mapping
6865   *outListType1=inListType;
6866   *outListType2=-1;
6867   if (inProt==Public)
6868   {
6869     switch (inListType) // in the private section of the derived class,
6870                         // the private section of the base class should not
6871                         // be visible
6872     {
6873       case MemberListType_priMethods:
6874       case MemberListType_priStaticMethods:
6875       case MemberListType_priSlots:
6876       case MemberListType_priAttribs:
6877       case MemberListType_priStaticAttribs:
6878       case MemberListType_priTypes:
6879         *outListType1=-1;
6880         *outListType2=-1;
6881         break;
6882       default:
6883         break;
6884     }
6885   }
6886   else if (inProt==Protected) // Protected inheritance
6887   {
6888     switch (inListType) // in the protected section of the derived class,
6889                         // both the public and protected members are shown
6890                         // as protected
6891     {
6892       case MemberListType_pubMethods:
6893       case MemberListType_pubStaticMethods:
6894       case MemberListType_pubSlots:
6895       case MemberListType_pubAttribs:
6896       case MemberListType_pubStaticAttribs:
6897       case MemberListType_pubTypes:
6898       case MemberListType_priMethods:
6899       case MemberListType_priStaticMethods:
6900       case MemberListType_priSlots:
6901       case MemberListType_priAttribs:
6902       case MemberListType_priStaticAttribs:
6903       case MemberListType_priTypes:
6904         *outListType1=-1;
6905         *outListType2=-1;
6906         break;
6907 
6908       case MemberListType_proMethods:
6909         *outListType2=MemberListType_pubMethods;
6910         break;
6911       case MemberListType_proStaticMethods:
6912         *outListType2=MemberListType_pubStaticMethods;
6913         break;
6914       case MemberListType_proSlots:
6915         *outListType2=MemberListType_pubSlots;
6916         break;
6917       case MemberListType_proAttribs:
6918         *outListType2=MemberListType_pubAttribs;
6919         break;
6920       case MemberListType_proStaticAttribs:
6921         *outListType2=MemberListType_pubStaticAttribs;
6922         break;
6923       case MemberListType_proTypes:
6924         *outListType2=MemberListType_pubTypes;
6925         break;
6926       default:
6927         break;
6928     }
6929   }
6930   else if (inProt==Private)
6931   {
6932     switch (inListType) // in the private section of the derived class,
6933                         // both the public and protected members are shown
6934                         // as private
6935     {
6936       case MemberListType_pubMethods:
6937       case MemberListType_pubStaticMethods:
6938       case MemberListType_pubSlots:
6939       case MemberListType_pubAttribs:
6940       case MemberListType_pubStaticAttribs:
6941       case MemberListType_pubTypes:
6942       case MemberListType_proMethods:
6943       case MemberListType_proStaticMethods:
6944       case MemberListType_proSlots:
6945       case MemberListType_proAttribs:
6946       case MemberListType_proStaticAttribs:
6947       case MemberListType_proTypes:
6948         *outListType1=-1;
6949         *outListType2=-1;
6950         break;
6951 
6952       case MemberListType_priMethods:
6953         if (extractPrivate)
6954         {
6955           *outListType1=MemberListType_pubMethods;
6956           *outListType2=MemberListType_proMethods;
6957         }
6958         else
6959         {
6960           *outListType1=-1;
6961           *outListType2=-1;
6962         }
6963         break;
6964       case MemberListType_priStaticMethods:
6965         if (extractPrivate)
6966         {
6967           *outListType1=MemberListType_pubStaticMethods;
6968           *outListType2=MemberListType_proStaticMethods;
6969         }
6970         else
6971         {
6972           *outListType1=-1;
6973           *outListType2=-1;
6974         }
6975         break;
6976       case MemberListType_priSlots:
6977         if (extractPrivate)
6978         {
6979           *outListType1=MemberListType_pubSlots;
6980           *outListType2=MemberListType_proSlots;
6981         }
6982         else
6983         {
6984           *outListType1=-1;
6985           *outListType2=-1;
6986         }
6987         break;
6988       case MemberListType_priAttribs:
6989         if (extractPrivate)
6990         {
6991           *outListType1=MemberListType_pubAttribs;
6992           *outListType2=MemberListType_proAttribs;
6993         }
6994         else
6995         {
6996           *outListType1=-1;
6997           *outListType2=-1;
6998         }
6999         break;
7000       case MemberListType_priStaticAttribs:
7001         if (extractPrivate)
7002         {
7003           *outListType1=MemberListType_pubStaticAttribs;
7004           *outListType2=MemberListType_proStaticAttribs;
7005         }
7006         else
7007         {
7008           *outListType1=-1;
7009           *outListType2=-1;
7010         }
7011         break;
7012       case MemberListType_priTypes:
7013         if (extractPrivate)
7014         {
7015           *outListType1=MemberListType_pubTypes;
7016           *outListType2=MemberListType_proTypes;
7017         }
7018         else
7019         {
7020           *outListType1=-1;
7021           *outListType2=-1;
7022         }
7023         break;
7024       default:
7025         break;
7026     }
7027   }
7028   //printf("convertProtectionLevel(type=%d prot=%d): %d,%d\n",
7029   //    inListType,inProt,*outListType1,*outListType2);
7030 }
7031 
mainPageHasTitle()7032 bool mainPageHasTitle()
7033 {
7034   return Doxygen::mainPage!=0 && Doxygen::mainPage->hasTitle();
7035 }
7036 
getDotImageExtension()7037 QCString getDotImageExtension()
7038 {
7039   QCString imgExt = Config_getEnumAsString(DOT_IMAGE_FORMAT);
7040   int i= imgExt.find(':'); // strip renderer part when using e.g. 'png:cairo:gd' as format
7041   return i==-1 ? imgExt : imgExt.left(i);
7042 }
7043 
openOutputFile(const QCString & outFile,std::ofstream & f)7044 bool openOutputFile(const QCString &outFile,std::ofstream &f)
7045 {
7046   assert(!f.is_open());
7047   bool fileOpened=FALSE;
7048   bool writeToStdout=outFile=="-";
7049   if (writeToStdout) // write to stdout
7050   {
7051     f.basic_ios<char>::rdbuf(std::cout.rdbuf());
7052     fileOpened = true;
7053   }
7054   else // write to file
7055   {
7056     FileInfo fi(outFile.str());
7057     if (fi.exists()) // create a backup
7058     {
7059       Dir dir;
7060       FileInfo backup(fi.fileName()+".bak");
7061       if (backup.exists()) // remove existing backup
7062         dir.remove(backup.fileName());
7063       dir.rename(fi.fileName(),fi.fileName()+".bak");
7064     }
7065     f.open(outFile.str(),std::ofstream::out | std::ofstream::binary);
7066     fileOpened = f.is_open();
7067   }
7068   return fileOpened;
7069 }
7070 
writeExtraLatexPackages(TextStream & t)7071 void writeExtraLatexPackages(TextStream &t)
7072 {
7073   // User-specified packages
7074   const StringVector &extraPackages = Config_getList(EXTRA_PACKAGES);
7075   if (!extraPackages.empty())
7076   {
7077     t << "% Packages requested by user\n";
7078     for (const auto &pkgName : extraPackages)
7079     {
7080       if ((pkgName[0] == '[') || (pkgName[0] == '{'))
7081         t << "\\usepackage" << pkgName.c_str() << "\n";
7082       else
7083         t << "\\usepackage{" << pkgName.c_str() << "}\n";
7084     }
7085     t << "\n";
7086   }
7087 }
7088 
writeLatexSpecialFormulaChars(TextStream & t)7089 void writeLatexSpecialFormulaChars(TextStream &t)
7090 {
7091     unsigned char minus[4]; // Superscript minus
7092     char *pminus = (char *)minus;
7093     unsigned char sup2[3]; // Superscript two
7094     char *psup2 = (char *)sup2;
7095     unsigned char sup3[3];
7096     char *psup3 = (char *)sup3; // Superscript three
7097     minus[0]= 0xE2;
7098     minus[1]= 0x81;
7099     minus[2]= 0xBB;
7100     minus[3]= 0;
7101     sup2[0]= 0xC2;
7102     sup2[1]= 0xB2;
7103     sup2[2]= 0;
7104     sup3[0]= 0xC2;
7105     sup3[1]= 0xB3;
7106     sup3[2]= 0;
7107 
7108     t << "\\usepackage{newunicodechar}\n"
7109          "  \\newunicodechar{" << pminus << "}{${}^{-}$}% Superscript minus\n"
7110          "  \\newunicodechar{" << psup2  << "}{${}^{2}$}% Superscript two\n"
7111          "  \\newunicodechar{" << psup3  << "}{${}^{3}$}% Superscript three\n"
7112          "\n";
7113 }
7114 
7115 //------------------------------------------------------
7116 // simplified way to know if this is fixed form
recognizeFixedForm(const QCString & contents,FortranFormat format)7117 bool recognizeFixedForm(const QCString &contents, FortranFormat format)
7118 {
7119   int column=0;
7120   bool skipLine=FALSE;
7121 
7122   if (format == FortranFormat_Fixed) return TRUE;
7123   if (format == FortranFormat_Free)  return FALSE;
7124 
7125   for (int i=0;;i++)
7126   {
7127     column++;
7128 
7129     switch(contents[i])
7130     {
7131       case '\n':
7132         column=0;
7133         skipLine=FALSE;
7134         break;
7135       case ' ':
7136         break;
7137       case '\000':
7138         return FALSE;
7139       case '#':
7140         skipLine=TRUE;
7141         break;
7142       case 'C':
7143       case 'c':
7144       case '*':
7145         if (column==1) return TRUE;
7146         if (skipLine) break;
7147         return FALSE;
7148       case '!':
7149         if (column>1 && column<7) return FALSE;
7150         skipLine=TRUE;
7151         break;
7152       default:
7153         if (skipLine) break;
7154         if (column>=7) return TRUE;
7155         return FALSE;
7156     }
7157   }
7158   return FALSE;
7159 }
7160 
convertFileNameFortranParserCode(QCString fn)7161 FortranFormat convertFileNameFortranParserCode(QCString fn)
7162 {
7163   QCString ext = getFileNameExtension(fn);
7164   QCString parserName = Doxygen::parserManager->getParserName(ext);
7165 
7166   if (parserName == "fortranfixed") return FortranFormat_Fixed;
7167   else if (parserName == "fortranfree") return FortranFormat_Free;
7168 
7169   return FortranFormat_Unknown;
7170 }
7171 //------------------------------------------------------------------------
7172 
7173 /// Clear a text block \a s from \a begin to \a end markers
clearBlock(const QCString & s,const QCString & begin,const QCString & end)7174 QCString clearBlock(const QCString &s,const QCString &begin,const QCString &end)
7175 {
7176   if (s.isEmpty() || begin.isEmpty() || end.isEmpty()) return s;
7177   const char *p, *q;
7178   int beginLen = (int)begin.length();
7179   int endLen = (int)end.length();
7180   int resLen = 0;
7181   for (p=s.data(); (q=strstr(p,begin.data()))!=0; p=q+endLen)
7182   {
7183     resLen+=(int)(q-p);
7184     p=q+beginLen;
7185     if ((q=strstr(p,end.data()))==0)
7186     {
7187       resLen+=beginLen;
7188       break;
7189     }
7190   }
7191   resLen+=qstrlen(p);
7192   // resLen is the length of the string without the marked block
7193 
7194   QCString result(resLen+1);
7195   char *r;
7196   for (r=result.rawData(), p=s.data(); (q=strstr(p,begin.data()))!=0; p=q+endLen)
7197   {
7198     int l = (int)(q-p);
7199     memcpy(r,p,l);
7200     r+=l;
7201     p=q+beginLen;
7202     if ((q=strstr(p,end.data()))==0)
7203     {
7204       memcpy(r,begin.data(),beginLen);
7205       r+=beginLen;
7206       break;
7207     }
7208   }
7209   qstrcpy(r,p);
7210   return result;
7211 }
7212 //----------------------------------------------------------------------
7213 
selectBlock(const QCString & s,const QCString & name,bool enable,OutputGenerator::OutputType o)7214 QCString selectBlock(const QCString& s,const QCString &name,bool enable, OutputGenerator::OutputType o)
7215 {
7216   // TODO: this is an expensive function that is called a lot -> optimize it
7217   QCString begin;
7218   QCString end;
7219   QCString nobegin;
7220   QCString noend;
7221   switch (o)
7222   {
7223     case OutputGenerator::Html:
7224       begin = "<!--BEGIN " + name + "-->";
7225       end = "<!--END " + name + "-->";
7226       nobegin = "<!--BEGIN !" + name + "-->";
7227       noend = "<!--END !" + name + "-->";
7228       break;
7229     case OutputGenerator::Latex:
7230       begin = "%%BEGIN " + name;
7231       end = "%%END " + name;
7232       nobegin = "%%BEGIN !" + name;
7233       noend = "%%END !" + name;
7234       break;
7235     default:
7236       break;
7237   }
7238 
7239   QCString result = s;
7240   if (enable)
7241   {
7242     result = substitute(result, begin, "");
7243     result = substitute(result, end, "");
7244     result = clearBlock(result, nobegin, noend);
7245   }
7246   else
7247   {
7248     result = substitute(result, nobegin, "");
7249     result = substitute(result, noend, "");
7250     result = clearBlock(result, begin, end);
7251   }
7252 
7253   return result;
7254 }
7255 
removeEmptyLines(const QCString & s)7256 QCString removeEmptyLines(const QCString &s)
7257 {
7258   BufStr out(s.length()+1);
7259   const char *p=s.data();
7260   if (p)
7261   {
7262     char c;
7263     while ((c=*p++))
7264     {
7265       if (c=='\n')
7266       {
7267         const char *e = p;
7268         while (*e==' ' || *e=='\t') e++;
7269         if (*e=='\n')
7270         {
7271           p=e;
7272         }
7273         else out.addChar(c);
7274       }
7275       else
7276       {
7277         out.addChar(c);
7278       }
7279     }
7280   }
7281   out.addChar('\0');
7282   //printf("removeEmptyLines(%s)=%s\n",qPrint(s),qPrint(out));
7283   return out.data();
7284 }
7285 
7286 /// split input string \a s by string delimiter \a delimiter.
7287 /// returns a vector of non-empty strings that are between the delimiters
split(const std::string & s,const std::string & delimiter)7288 StringVector split(const std::string &s,const std::string &delimiter)
7289 {
7290   StringVector result;
7291   size_t prev = 0, pos = 0, len = s.length();
7292   do
7293   {
7294     pos = s.find(delimiter, prev);
7295     if (pos == std::string::npos) pos = len;
7296     if (pos>prev) result.push_back(s.substr(prev,pos-prev));
7297     prev = pos + delimiter.length();
7298   }
7299   while (pos<len && prev<len);
7300   return result;
7301 }
7302 
7303 /// split input string \a s by regular expression delimiter \a delimiter.
7304 /// returns a vector of non-empty strings that are between the delimiters
split(const std::string & s,const reg::Ex & delimiter)7305 StringVector split(const std::string &s,const reg::Ex &delimiter)
7306 {
7307   StringVector result;
7308   reg::Iterator iter(s, delimiter);
7309   reg::Iterator end;
7310   size_t p=0;
7311   for ( ; iter != end; ++iter)
7312   {
7313     const auto &match = *iter;
7314     size_t i=match.position();
7315     size_t l=match.length();
7316     if (i>p) result.push_back(s.substr(p,i-p));
7317     p=i+l;
7318   }
7319   if (p<s.length()) result.push_back(s.substr(p));
7320   return result;
7321 }
7322 
7323 /// find the index of a string in a vector of strings, returns -1 if the string could not be found
findIndex(const StringVector & sv,const std::string & s)7324 int findIndex(const StringVector &sv,const std::string &s)
7325 {
7326   auto it = std::find(sv.begin(),sv.end(),s);
7327   return it!=sv.end() ? (int)(it-sv.begin()) : -1;
7328 }
7329 
7330 /// find the index of the first occurrence of pattern \a re in a string \a s
7331 /// returns -1 if the pattern could not be found
findIndex(const std::string & s,const reg::Ex & re)7332 int findIndex(const std::string &s,const reg::Ex &re)
7333 {
7334   reg::Match match;
7335   return reg::search(s,match,re) ? (int)match.position() : -1;
7336 }
7337 
7338 /// create a string where the string in the vector are joined by the given delimiter
join(const StringVector & sv,const std::string & delimiter)7339 std::string join(const StringVector &sv,const std::string &delimiter)
7340 {
7341   std::string result;
7342   bool first=true;
7343   for (const auto &s : sv)
7344   {
7345     if (!first) result+=delimiter;
7346     first=false;
7347     result+=s;
7348   }
7349   return result;
7350 }
7351 
integerToAlpha(int n,bool upper)7352 QCString integerToAlpha(int n, bool upper)
7353 {
7354   QCString result;
7355   int residual = n;
7356 
7357   char modVal[2];
7358   modVal[1] = 0;
7359   while (residual > 0)
7360   {
7361     modVal[0] = (upper ? 'A': 'a') + (residual-1)%26;
7362     result = modVal + result;
7363     residual = (residual-1) / 26;
7364   }
7365   return result;
7366 }
7367 
integerToRoman(int n,bool upper)7368 QCString integerToRoman(int n, bool upper)
7369 {
7370   static const char *str_romans_upper[] = {  "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
7371   static const char *str_romans_lower[] = {  "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" };
7372   static const int values[]             = { 1000,  900, 500,  400, 100,   90,  50,   40,  10,    9,   5,    4,   1 };
7373   static const char **str_romans = upper ? str_romans_upper : str_romans_lower;
7374 
7375   QCString result;
7376   int residual = n;
7377 
7378   for (int i = 0; i < 13; ++i)
7379   {
7380     while (residual - values[i] >= 0)
7381     {
7382       result += str_romans[i];
7383       residual -= values[i];
7384     }
7385   }
7386 
7387   return result;
7388 }
7389 
7390