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 <)
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("<"); break;
3959 case '>': growBuf.addStr(">"); 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("&");
3977 }
3978 }
3979 else
3980 {
3981 growBuf.addStr("&");
3982 }
3983 break;
3984 case '\'': growBuf.addStr("'"); break;
3985 case '"': growBuf.addStr("""); 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> 
</literallayout>"); growBuf.addChar(c); break;
4012 case '<': growBuf.addStr("<"); break;
4013 case '>': growBuf.addStr(">"); 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("&");
4030 }
4031 else
4032 {
4033 growBuf.addStr(HtmlEntityMapper::instance()->docbook(res));
4034 q++;
4035 p = q;
4036 }
4037 }
4038 else
4039 {
4040 growBuf.addStr("&");
4041 }
4042 break;
4043 case '\'': growBuf.addStr("'"); break;
4044 case '"': growBuf.addStr("""); 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("$");
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("<"); break;
4075 case '>': growBuf.addStr(">"); 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("&");
4093 }
4094 }
4095 else
4096 {
4097 growBuf.addStr("&");
4098 }
4099 break;
4100 case '\'': growBuf.addStr("'"); break;
4101 case '"': growBuf.addStr("""); break;
4102 default:
4103 {
4104 uchar uc = static_cast<uchar>(c);
4105 if (uc<32 && !isspace(c))
4106 {
4107 growBuf.addStr("$");
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(" | "); } else first=false;
4914 if (recursivelyAddGroupListToTitle(ol, gd, FALSE))
4915 {
4916 ol.writeString(" » ");
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