Doxygen
configimpl.l
浏览该文件的文档.
1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2020 by Dimitri van Heesch.
4  *
5  * Permission to use, copy, modify, and distribute this software and its
6  * documentation under the terms of the GNU General Public License is hereby
7  * granted. No representations are made about the suitability of this software
8  * for any purpose. It is provided "as is" without express or implied warranty.
9  * See the GNU General Public License for more details.
10  *
11  */
12 %option never-interactive
13 %option prefix="configimplYY"
14 %top{
15 #include <stdint.h>
16 }
17 
18 %{
19 
20 /*
21  * includes
22  */
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <ctype.h>
27 #include <stdarg.h>
28 #include <errno.h>
29 #include <thread>
30 #include <algorithm>
31 #include <fstream>
32 #include <iostream>
33 
34 #include "regex.h"
35 #include "configimpl.h"
36 #include "version.h"
37 #include "portable.h"
38 #include "message.h"
39 #include "lang_cfg.h"
40 #include "language.h"
41 #include "configoptions.h"
42 #include "fileinfo.h"
43 #include "dir.h"
44 #include "textstream.h"
45 
46 #define YY_NO_INPUT 1
47 #define YY_NO_UNISTD_H 1
48 
49 #define USE_STATE2STRING 0
50 
51 #if USE_STATE2STRING
52 static const char *stateToString(int state);
53 #endif
54 
55 static const char *warning_str = "warning: ";
56 static const char *error_str = "error: ";
57 
58 void config_err(const char *fmt, ...)
59 {
60  va_list args;
61  va_start(args, fmt);
62  vfprintf(stderr, qPrint(QCString(error_str) + fmt), args);
63  va_end(args);
64 }
65 void config_term(const char *fmt, ...)
66 {
67  va_list args;
68  va_start(args, fmt);
69  vfprintf(stderr, qPrint(QCString(error_str) + fmt), args);
70  va_end(args);
71  fprintf(stderr, "%s\n", "Exiting...");
72  exit(1);
73 }
74 
75 void config_warn(const char *fmt, ...)
76 {
77  va_list args;
78  va_start(args, fmt);
79  vfprintf(stderr, qPrint(QCString(warning_str) + fmt), args);
80  va_end(args);
81 }
82 
83 static QCString configStringRecode(
84  const QCString &str,
85  const QCString &fromEncoding,
86  const QCString &toEncoding);
87 
88 #define MAX_INCLUDE_DEPTH 10
89 #define YY_NEVER_INTERACTIVE 1
90 
91 /* -----------------------------------------------------------------
92  */
93 static QCString convertToComment(const QCString &s, const QCString &u)
94 {
95  //printf("convertToComment(%s)=%s\n",qPrint(s),qPrint(u));
96  QCString result;
97  if (!s.isEmpty())
98  {
99  QCString tmp=s.stripWhiteSpace();
100  const char *p=tmp.data();
101  char c;
102  if (p)
103  {
104  result+="#";
105  if (*p && *p!='\n')
106  {
107  result+=" ";
108  }
109  while ((c=*p++))
110  {
111  if (c=='\n')
112  {
113  result+="\n#";
114  if (*p && *p!='\n')
115  {
116  result+=" ";
117  }
118  }
119  else result+=c;
120  }
121  result+='\n';
122  }
123  }
124  if (!u.isEmpty())
125  {
126  if (!result.isEmpty()) result+='\n';
127  result+= u;
128  }
129  return result;
130 }
131 
132 void ConfigOption::writeBoolValue(TextStream &t,bool v,bool initSpace)
133 {
134  if (initSpace) t << " ";
135  if (v) t << "YES"; else t << "NO";
136 }
137 
138 void ConfigOption::writeIntValue(TextStream &t,int i,bool initSpace)
139 {
140  if (initSpace) t << " ";
141  t << i;
142 }
143 
144 void ConfigOption::writeStringValue(TextStream &t,const QCString &s,bool initSpace)
145 {
146  char c;
147  bool needsEscaping=FALSE;
148  // convert the string back to it original g_encoding
149  QCString se = configStringRecode(s,"UTF-8",m_encoding);
150  if (se.isEmpty()) return;
151  const char *p=se.data();
152  if (p)
153  {
154  if (initSpace) t << " ";
155  while ((c=*p++)!=0 && !needsEscaping)
156  needsEscaping = (c==' ' || c== ',' || c=='\n' || c=='\t' || c=='"' || c=='#');
157  if (needsEscaping)
158  {
159  t << "\"";
160  p=se.data();
161  while (*p)
162  {
163  if (*p==' ' && *(p+1)=='\0') break; // skip inserted space at the end
164  if (*p=='"') t << "\\"; // escape quotes
165  t << *p++;
166  }
167  t << "\"";
168  }
169  else
170  {
171  t << se;
172  }
173  }
174 }
175 
176 void ConfigOption::writeStringList(TextStream &t,const StringVector &l)
177 {
178  bool first=TRUE;
179  for (const auto &p : l)
180  {
181  if (!first) t << " \\\n";
182  QCString s=p.c_str();
183  if (!first)
184  t << " ";
185  writeStringValue(t,s);
186  first=FALSE;
187  }
188 }
189 
190 /* -----------------------------------------------------------------
191  */
192 
193 ConfigImpl *ConfigImpl::m_instance = 0;
194 
195 void ConfigInt::convertStrToVal()
196 {
197  if (!m_valueString.isEmpty())
198  {
199  bool ok;
200  int val = m_valueString.toInt(&ok);
201  if (!ok || val<m_minVal || val>m_maxVal)
202  {
203  config_warn("argument '%s' for option %s is not a valid number in the range [%d..%d]!\n"
204  "Using the default: %d!\n",qPrint(m_valueString),qPrint(m_name),m_minVal,m_maxVal,m_value);
205  }
206  else
207  {
208  m_value=val;
209  }
210  }
211 }
212 
213 static bool convertStringToBool(const QCString &str,bool &isValid)
214 {
215  isValid=false;
216  QCString val = str.stripWhiteSpace().lower();
217  if (!val.isEmpty())
218  {
219  if (val=="yes" || val=="true" || val=="1" || val=="all")
220  {
221  isValid=true;
222  return true;
223  }
224  else if (val=="no" || val=="false" || val=="0" || val=="none")
225  {
226  isValid=true;
227  return false;
228  }
229  }
230  return false;
231 }
232 
233 void ConfigBool::convertStrToVal()
234 {
235  if (!m_valueString.stripWhiteSpace().isEmpty())
236  {
237  bool isValid=false;
238  bool b = convertStringToBool(m_valueString,isValid);
239  if (isValid)
240  {
241  m_value=b;
242  }
243  else
244  {
245  config_warn("argument '%s' for option %s is not a valid boolean value\n"
246  "Using the default: %s!\n",qPrint(m_valueString),qPrint(m_name),m_value?"YES":"NO");
247  }
248  }
249 }
250 
251 void ConfigEnum::convertStrToVal()
252 {
253  if (m_value.isEmpty())
254  {
255  m_value = m_defValue;
256  return;
257  }
258  QCString val = m_value.stripWhiteSpace().lower();
259  for (const auto &s : m_valueRange)
260  {
261  if (s.lower() == val)
262  {
263  m_value = s;
264  return;
265  }
266  }
267 
268  config_warn("argument '%s' for option %s is not a valid enum value\n"
269  "Using the default: %s!\n",qPrint(m_value),qPrint(m_name),qPrint(m_defValue));
270  m_value = m_defValue;
271 }
272 
273 QCString &ConfigImpl::getString(const char *fileName,int num,const char *name) const
274 {
275  auto it = m_dict.find(name);
276  if (it==m_dict.end())
277  {
278  config_term("%s<%d>: Internal error: Requested unknown option %s!\n",fileName,num,name);
279  }
280  else if (it->second->kind()!=ConfigOption::O_String)
281  {
282  config_term("%s<%d>: Internal error: Requested option %s not of string type!\n",fileName,num,name);
283  }
284  return *((ConfigString *)it->second)->valueRef();
285 }
286 
287 StringVector &ConfigImpl::getList(const char *fileName,int num,const char *name) const
288 {
289  auto it = m_dict.find(name);
290  if (it==m_dict.end())
291  {
292  config_term("%s<%d>: Internal error: Requested unknown option %s!\n",fileName,num,name);
293  }
294  else if (it->second->kind()!=ConfigOption::O_List)
295  {
296  config_term("%s<%d>: Internal error: Requested option %s not of list type!\n",fileName,num,name);
297  }
298  return *((ConfigList *)it->second)->valueRef();
299 }
300 
301 QCString &ConfigImpl::getEnum(const char *fileName,int num,const char *name) const
302 {
303  auto it = m_dict.find(name);
304  if (it==m_dict.end())
305  {
306  config_term("%s<%d>: Internal error: Requested unknown option %s!\n",fileName,num,name);
307  }
308  else if (it->second->kind()!=ConfigOption::O_Enum)
309  {
310  config_term("%s<%d>: Internal error: Requested option %s not of enum type!\n",fileName,num,name);
311  }
312  return *((ConfigEnum *)it->second)->valueRef();
313 }
314 
315 int &ConfigImpl::getInt(const char *fileName,int num,const char *name) const
316 {
317  auto it = m_dict.find(name);
318  if (it==m_dict.end())
319  {
320  config_term("%s<%d>: Internal error: Requested unknown option %s!\n",fileName,num,name);
321  }
322  else if (it->second->kind()!=ConfigOption::O_Int)
323  {
324  config_term("%s<%d>: Internal error: Requested option %s not of integer type!\n",fileName,num,name);
325  }
326  return *((ConfigInt *)it->second)->valueRef();
327 }
328 
329 bool &ConfigImpl::getBool(const char *fileName,int num,const char *name) const
330 {
331  auto it = m_dict.find(name);
332  if (it==m_dict.end())
333  {
334  config_term("%s<%d>: Internal error: Requested unknown option %s!\n",fileName,num,name);
335  }
336  else if (it->second->kind()!=ConfigOption::O_Bool)
337  {
338  config_term("%s<%d>: Internal error: Requested option %s not of boolean type!\n",fileName,num,name);
339  }
340  return *((ConfigBool *)it->second)->valueRef();
341 }
342 
343 /* ------------------------------------------ */
344 
345 void ConfigInfo::writeTemplate(TextStream &t, bool sl,bool)
346 {
347  if (!sl)
348  {
349  t << "\n";
350  }
351  t << "#---------------------------------------------------------------------------\n";
352  t << "# " << m_doc << "\n";
353  t << "#---------------------------------------------------------------------------\n";
354 }
355 
356 void ConfigList::writeTemplate(TextStream &t,bool sl,bool)
357 {
358  if (!sl)
359  {
360  t << "\n";
361  t << convertToComment(m_doc, m_userComment);
362  t << "\n";
363  }
364  else if (!m_userComment.isEmpty())
365  {
366  t << convertToComment("", m_userComment);
367  }
368  t << m_name << m_spaces.left(MAX_OPTION_LENGTH-m_name.length()) << "=";
369  writeStringList(t,m_value);
370  t << "\n";
371 }
372 
373 bool ConfigList::isDefault()
374 {
375  auto get_stripped = [](std::string s) { return QCString(s.c_str()).stripWhiteSpace(); };
376  auto is_not_empty = [get_stripped](std::string s) { return !get_stripped(s).isEmpty(); };
377  size_t defCnt = std::count_if( m_value.begin(), m_value.end(),is_not_empty);
378  size_t valCnt = std::count_if(m_defaultValue.begin(),m_defaultValue.end(),is_not_empty);
379  if ( valCnt != defCnt)
380  {
381  return false;
382  }
383  auto it1 = m_value.begin();
384  auto it2 = m_defaultValue.begin();
385  while (it1!=m_value.end() && it2!=m_defaultValue.end())
386  {
387  // skip over empty values
388  while (it1!=m_value.end() && !is_not_empty(*it1))
389  {
390  ++it1;
391  }
392  if (it1!=m_value.end()) // non-empty value
393  {
394  if (get_stripped(*it1) != get_stripped(*it2)) // not the default, write as difference
395  {
396  return false;
397  }
398  ++it1;
399  ++it2;
400  }
401  }
402  return true;
403 }
404 
405 void ConfigList::compareDoxyfile(TextStream &t)
406 {
407  if (!isDefault()) writeTemplate(t,TRUE,TRUE);
408 }
409 
410 void ConfigList::writeXMLDoxyfile(TextStream &t)
411 {
412  t << " <option id='" << m_name << "'";
413  t << " default='" << (isDefault() ? "yes" : "no") << "'";
414  t << " type='stringlist'";
415  t << ">";
416  t << "\n";
417  for (const auto &p : m_value)
418  {
419  QCString s=p.c_str();
420  t << " <value>";
421  t << "<![CDATA[";
422  writeStringValue(t,s,false);
423  t << "]]>";
424  t << "</value>\n";
425  }
426  t << " </option>\n";
427 }
428 
429 void ConfigEnum::writeTemplate(TextStream &t,bool sl,bool)
430 {
431  if (!sl)
432  {
433  t << "\n";
434  t << convertToComment(m_doc, m_userComment);
435  t << "\n";
436  }
437  else if (!m_userComment.isEmpty())
438  {
439  t << convertToComment("", m_userComment);
440  }
441  t << m_name << m_spaces.left(MAX_OPTION_LENGTH-m_name.length()) << "=";
442  writeStringValue(t,m_value);
443  t << "\n";
444 }
445 
446 void ConfigEnum::compareDoxyfile(TextStream &t)
447 {
448  if (!isDefault()) writeTemplate(t,TRUE,TRUE);
449 }
450 
451 void ConfigEnum::writeXMLDoxyfile(TextStream &t)
452 {
453  t << " <option id='" << m_name << "'";
454  t << " default='" << (isDefault() ? "yes" : "no") << "'";
455  t << " type='string'";
456  t << ">";
457  t << "<value>";
458  writeStringValue(t,m_value,false);
459  t << "</value>";
460  t << "</option>\n";
461 }
462 
463 void ConfigString::writeTemplate(TextStream &t,bool sl,bool)
464 {
465  if (!sl)
466  {
467  t << "\n";
468  t << convertToComment(m_doc, m_userComment);
469  t << "\n";
470  }
471  else if (!m_userComment.isEmpty())
472  {
473  t << convertToComment("", m_userComment);
474  }
475  t << m_name << m_spaces.left(MAX_OPTION_LENGTH-m_name.length()) << "=";
476  writeStringValue(t,m_value);
477  t << "\n";
478 }
479 
480 void ConfigString::compareDoxyfile(TextStream &t)
481 {
482  if (!isDefault()) writeTemplate(t,TRUE,TRUE);
483 }
484 
485 void ConfigString::writeXMLDoxyfile(TextStream &t)
486 {
487  t << " <option id='" << m_name << "'";
488  t << " default='" << (isDefault() ? "yes" : "no") << "'";
489  t << " type='string'";
490  t << ">";
491  t << "<value>";
492  t << "<![CDATA[";
493  writeStringValue(t,m_value,false);
494  t << "]]>";
495  t << "</value>";
496  t << "</option>\n";
497 }
498 
499 void ConfigInt::writeTemplate(TextStream &t,bool sl,bool upd)
500 {
501  if (!sl)
502  {
503  t << "\n";
504  t << convertToComment(m_doc, m_userComment);
505  t << "\n";
506  }
507  else if (!m_userComment.isEmpty())
508  {
509  t << convertToComment("", m_userComment);
510  }
511  t << m_name << m_spaces.left(MAX_OPTION_LENGTH-m_name.length()) << "=";
512  if (upd && !m_valueString.isEmpty())
513  {
514  writeStringValue(t,m_valueString);
515  }
516  else
517  {
518  writeIntValue(t,m_value);
519  }
520  t << "\n";
521 }
522 
523 void ConfigInt::compareDoxyfile(TextStream &t)
524 {
525  if (!isDefault()) writeTemplate(t,TRUE,TRUE);
526 }
527 
528 void ConfigInt::writeXMLDoxyfile(TextStream &t)
529 {
530  t << " <option id='" << m_name << "'";
531  t << " default='" << (isDefault() ? "yes" : "no") << "'";
532  t << " type='int'";
533  t << ">";
534  t << "<value>";
535  writeIntValue(t,m_value,false);
536  t << "</value>";
537  t << "</option>\n";
538 }
539 
540 void ConfigBool::writeTemplate(TextStream &t,bool sl,bool upd)
541 {
542  if (!sl)
543  {
544  t << "\n";
545  t << convertToComment(m_doc, m_userComment);
546  t << "\n";
547  }
548  else if (!m_userComment.isEmpty())
549  {
550  t << convertToComment("", m_userComment);
551  }
552  QCString spaces = m_spaces.left(MAX_OPTION_LENGTH-m_name.length());
553  t << m_name << spaces << "=";
554  if (upd && !m_valueString.isEmpty())
555  {
556  writeStringValue(t,m_valueString);
557  }
558  else
559  {
560  writeBoolValue(t,m_value);
561  }
562  t << "\n";
563 }
564 
565 void ConfigBool::compareDoxyfile(TextStream &t)
566 {
567  if (!isDefault()) writeTemplate(t,TRUE,TRUE);
568 }
569 
570 void ConfigBool::writeXMLDoxyfile(TextStream &t)
571 {
572  t << " <option id='" << m_name << "'";
573  t << " default='" << (isDefault() ? "yes" : "no") << "'";
574  t << " type='bool'";
575  t << ">";
576  t << "<value>";
577  writeBoolValue(t,m_value,false);
578  t << "</value>";
579  t << "</option>\n";
580 }
581 
582 void ConfigObsolete::writeTemplate(TextStream &,bool,bool) {}
583 void ConfigDisabled::writeTemplate(TextStream &,bool,bool) {}
584 
585 /* -----------------------------------------------------------------
586  *
587  * static variables
588  */
589 
590 struct ConfigFileState
591 {
592  int lineNr;
593  FILE *filePtr;
594  YY_BUFFER_STATE oldState;
595  YY_BUFFER_STATE newState;
596  QCString fileName;
597 };
598 
599 static const char *g_inputString;
600 static int g_inputPosition;
601 static int g_yyLineNr;
602 static QCString g_yyFileName;
603 static QCString g_cmd;
604 static QCString *g_string=0;
605 static StringVector *g_list=0;
606 static QCString g_listStr;
607 static StringVector g_includePathList;
608 static std::vector< std::unique_ptr<ConfigFileState> > g_includeStack;
609 static bool g_configUpdate = FALSE;
610 static QCString g_encoding;
611 static ConfigImpl *g_config;
612 
613 /* -----------------------------------------------------------------
614  */
615 #undef YY_INPUT
616 #define YY_INPUT(buf,result,max_size) result=yyread(buf,max_size);
617 
618 static yy_size_t yyread(char *buf,yy_size_t max_size)
619 {
620  // no file included
621  if (g_includeStack.empty())
622  {
623  yy_size_t c=0;
624  if (g_inputString==0) return c;
625  while( c < max_size && g_inputString[g_inputPosition] )
626  {
627  *buf = g_inputString[g_inputPosition++] ;
628  c++; buf++;
629  }
630  return c;
631  }
632  else
633  {
634  //assert(g_includeStack.current()->newState==YY_CURRENT_BUFFER);
635  return (yy_size_t)fread(buf,1,max_size,g_includeStack.back()->filePtr);
636  }
637 }
638 
639 
640 static QCString configStringRecode(
641  const QCString &str,
642  const QCString &inputEncoding,
643  const QCString &outputEncoding)
644 {
645  if (inputEncoding.isEmpty() || outputEncoding.isEmpty() || inputEncoding==outputEncoding) return str;
646  int inputSize=str.length();
647  int outputSize=inputSize*4+1;
648  QCString output(outputSize);
649  void *cd = portable_iconv_open(outputEncoding.data(),inputEncoding.data());
650  if (cd==(void *)(-1))
651  {
652  config_term("Error: unsupported character conversion: '%s'->'%s'\n",
653  qPrint(inputEncoding),qPrint(outputEncoding));
654  }
655  size_t iLeft=(size_t)inputSize;
656  size_t oLeft=(size_t)outputSize;
657  const char *inputPtr = str.data();
658  char *outputPtr = output.rawData();
659  if (!portable_iconv(cd, &inputPtr, &iLeft, &outputPtr, &oLeft))
660  {
661  outputSize-=(int)oLeft;
662  output.resize(outputSize+1);
663  output.at(outputSize)='\0';
664  //printf("iconv: input size=%d output size=%d\n[%s]\n",size,newSize,qPrint(srcBuf));
665  }
666  else
667  {
668  config_term("Error: failed to translate characters from %s to %s: %s\n",
669  qPrint(inputEncoding),qPrint(outputEncoding),strerror(errno));
670  }
671  portable_iconv_close(cd);
672  return output;
673 }
674 
675 static void checkEncoding()
676 {
677  ConfigString *option = (ConfigString*)g_config->get("DOXYFILE_ENCODING");
678  g_encoding = *option->valueRef();
679 }
680 
681 static QCString stripComment(const QCString &s)
682 {
683  // check if there is a comment at the end of the string
684  bool insideQuote=false;
685  int l = s.length();
686  for (int i=0;i<l;i++)
687  {
688  char c = s.at(i);
689  if (c=='\\') // skip over escaped characters
690  {
691  i++;
692  }
693  else if (c=='"') // toggle inside/outside quotation
694  {
695  insideQuote=!insideQuote;
696  }
697  else if (!insideQuote && c=='#') // found start of a comment
698  {
699  if (i<l-1 && s.at(i+1)=='#') // ## -> user comment
700  {
701  g_config->appendUserComment(s.mid(i)+"\n");
702  }
703  return s.left(i).stripWhiteSpace();
704  }
705  }
706  return s;
707 }
708 
709 static void processString()
710 {
711  // strip leading and trailing whitespace
712  QCString s = stripComment(g_string->stripWhiteSpace());
713  int l = s.length();
714 
715  // remove surrounding quotes if present (and not escaped)
716  if (l>=2 && s.at(0)=='"' && s.at(l-1)=='"' && // remove quotes
717  (s.at(l-2)!='\\' || (s.at(l-2)=='\\' && s.at(l-3)=='\\')))
718  {
719  s=s.mid(1,s.length()-2);
720  l=s.length();
721  }
722 
723  // check for invalid and/or escaped quotes
724  bool warned=false;
725  QCString result;
726  for (int i=0;i<l;i++)
727  {
728  char c = s.at(i);
729  if (c=='\\') // escaped character
730  {
731  if (i<l-1 && s.at(i+1)=='"') // unescape the quote character
732  {
733  result+='"';
734  }
735  else // keep other escaped characters in escaped form
736  {
737  result+=c;
738  if (i<l-1)
739  {
740  result+=s.at(i+1);
741  }
742  }
743  i++; // skip over the escaped character
744  }
745  else if (c=='"') // unescaped quote
746  {
747  if (!warned)
748  {
749  config_warn("Invalid value for '%s' tag at line %d, file %s: Value '%s' is not properly quoted\n",
750  qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName),qPrint(g_string->stripWhiteSpace()));
751  }
752  warned=true;
753  }
754  else // normal character
755  {
756  result+=c;
757  }
758  }
759 
760  // recode the string
761  *g_string=configStringRecode(result,g_encoding,"UTF-8");
762 
763  // update encoding
764  checkEncoding();
765 
766  //printf("Processed string '%s'\n",qPrint(g_string));
767 }
768 
769 static void processList()
770 {
771  bool allowCommaAsSeparator = g_cmd!="PREDEFINED";
772 
773  QCString s = stripComment(g_listStr.stripWhiteSpace());
774  int l = s.length();
775 
776  QCString elemStr;
777 
778  // helper to push elemStr to the list and clear it
779  auto addElem = [&elemStr]()
780  {
781  if (!elemStr.isEmpty())
782  {
783  QCString e = configStringRecode(elemStr,g_encoding,"UTF-8");
784  //printf("Processed list element '%s'\n",qPrint(e));
785  g_list->push_back(e.str());
786  elemStr="";
787  }
788  };
789 
790  bool needsSeparator=false;
791  int insideQuote=false;
792  bool warned=false;
793  for (int i=0;i<l;i++)
794  {
795  char c = s.at(i);
796  if (!needsSeparator && c=='\\') // escaped character
797  {
798  if (i<l-1 && s.at(i+1)=='"') // unescape the quote character
799  {
800  elemStr+='"';
801  }
802  else // keep other escaped characters in escaped form
803  {
804  elemStr+=c;
805  if (i<l-1)
806  {
807  elemStr+=s.at(i+1);
808  }
809  }
810  i++; // skip over the escaped character
811  }
812  else if (!needsSeparator && c=='"') // quote character
813  {
814  if (!insideQuote)
815  {
816  insideQuote=true;
817  }
818  else // this quote ends an element
819  {
820  insideQuote=false;
821  needsSeparator=true;
822  }
823  }
824  else if (!insideQuote && ((c==',' && allowCommaAsSeparator) || isspace(c))) // separator
825  {
826  needsSeparator=false;
827  addElem();
828  }
829  else // normal content character
830  {
831  if (needsSeparator)
832  {
833  if (!warned)
834  {
835  config_warn("Invalid value for '%s' tag at line %d, file %s: Values in list '%s' are not properly space %sseparated\n",
836  qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName),qPrint(g_listStr.stripWhiteSpace()),allowCommaAsSeparator?"or comma ":"");
837  warned=true;
838  }
839  needsSeparator=false;
840  i--; // try the character again as part of a new element
841  addElem();
842  }
843  else
844  {
845  elemStr+=c;
846  }
847  }
848  }
849  // add last part
850  addElem();
851  if (insideQuote)
852  {
853  config_warn("Invalid value for '%s' tag at line %d, file %s: Values in list '%s' are not properly quoted\n",
854  qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName),qPrint(g_listStr.stripWhiteSpace()));
855  }
856 }
857 
858 static FILE *tryPath(const QCString &path,const QCString &fileName)
859 {
860  QCString absName=(!path.isEmpty() ? path+"/"+fileName : fileName);
861  FileInfo fi(absName.str());
862  if (fi.exists() && fi.isFile())
863  {
864  FILE *f=Portable::fopen(absName,"r");
865  if (!f) config_err("could not open file %s for reading\n",qPrint(absName));
866  return f;
867  }
868  return 0;
869 }
870 
871 static void substEnvVarsInStrList(StringVector &sl);
872 static void substEnvVarsInString(QCString &s);
873 
874 static FILE *findFile(const QCString &fileName)
875 {
876  if (fileName.isEmpty())
877  {
878  return 0;
879  }
880  if (Portable::isAbsolutePath(fileName))
881  {
882  return tryPath(QCString(), fileName);
883  }
884  substEnvVarsInStrList(g_includePathList);
885  for (const auto &s : g_includePathList)
886  {
887  FILE *f = tryPath(s.c_str(),fileName);
888  if (f) return f;
889  }
890  // try cwd if g_includePathList fails
891  return tryPath(".",fileName);
892 }
893 
894 static void readIncludeFile(const QCString &incName)
895 {
896  if (g_includeStack.size()==MAX_INCLUDE_DEPTH) {
897  config_term("maximum include depth (%d) reached, %s is not included. Aborting...\n",
898  MAX_INCLUDE_DEPTH,qPrint(incName));
899  }
900 
901  QCString inc = incName;
902  substEnvVarsInString(inc);
903  inc = inc.stripWhiteSpace();
904  uint incLen = inc.length();
905  if (incLen>0 && inc.at(0)=='"' && inc.at(incLen-1)=='"') // strip quotes
906  {
907  inc=inc.mid(1,incLen-2);
908  }
909 
910  FILE *f;
911 
912  if ((f=findFile(inc))) // see if the include file can be found
913  {
914  // For debugging
915 #if SHOW_INCLUDES
916  for (size_t i=0;i<g_includeStack.size();i++) msg(" ");
917  msg("@INCLUDE = %s: parsing...\n",qPrint(inc));
918 #endif
919 
920  // store the state of the old file
921  ConfigFileState *fs=new ConfigFileState;
922  fs->oldState=YY_CURRENT_BUFFER;
923  fs->lineNr=g_yyLineNr;
924  fs->fileName=g_yyFileName;
925  fs->filePtr=f;
926  // push the state on the stack
927  g_includeStack.push_back(std::unique_ptr<ConfigFileState>(fs));
928  // set the scanner to the include file
929  yy_switch_to_buffer(yy_create_buffer(f, YY_BUF_SIZE));
930  fs->newState=YY_CURRENT_BUFFER;
931  g_yyFileName=inc;
932  }
933  else
934  {
935  config_term("@INCLUDE = %s: not found!\n",qPrint(inc));
936  }
937 }
938 
939 
940 %}
941 
942 %option noyywrap
943 %option nounput
944 
945 %x Start
946 %x SkipInvalid
947 %x GetString
948 %x GetStrList
949 %x Include
950 
951 %%
952 
953 <*>\0x0d
954 
955  /*-------------- Comments ---------------*/
956 
957 <Start>"##".*"\n" {
958  g_config->appendUserComment(yytext);
959  g_yyLineNr++;
960  }
961 <Start>"#".*"\n" { /* normal comment */
962  g_yyLineNr++;
963  }
964 
965  /*-------------- TAG start ---------------*/
966 
967 <Start>[a-z_A-Z][a-z_A-Z0-9]*[ \t]*"=" { g_cmd=yytext;
968  g_cmd=g_cmd.left(g_cmd.length()-1).stripWhiteSpace();
969  ConfigOption *option = g_config->get(g_cmd);
970  if (option==0) // oops not known
971  {
972  config_warn("ignoring unsupported tag '%s' at line %d, file %s\n",
973  qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
974  BEGIN(SkipInvalid);
975  }
976  else // known tag
977  {
978  option->setUserComment(g_config->takeUserComment());
979  option->setEncoding(g_encoding);
980  switch(option->kind())
981  {
982  case ConfigOption::O_Info:
983  // shouldn't get here!
984  BEGIN(SkipInvalid);
985  break;
986  case ConfigOption::O_List:
987  g_list = ((ConfigList *)option)->valueRef();
988  g_list->clear();
989  g_listStr="";
990  BEGIN(GetStrList);
991  break;
992  case ConfigOption::O_Enum:
993  g_string = ((ConfigEnum *)option)->valueRef();
994  g_string->resize(0);
995  BEGIN(GetString);
996  break;
997  case ConfigOption::O_String:
998  g_string = ((ConfigString *)option)->valueRef();
999  g_string->resize(0);
1000  BEGIN(GetString);
1001  break;
1002  case ConfigOption::O_Int:
1003  g_string = ((ConfigInt *)option)->valueStringRef();
1004  g_string->resize(0);
1005  BEGIN(GetString);
1006  break;
1007  case ConfigOption::O_Bool:
1008  g_string = ((ConfigBool *)option)->valueStringRef();
1009  g_string->resize(0);
1010  BEGIN(GetString);
1011  break;
1012  case ConfigOption::O_Obsolete:
1013  if (g_configUpdate)
1014  {
1015  config_warn("Tag '%s' at line %d of file '%s' has become obsolete.\n"
1016  " This tag has been removed.\n", qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
1017  }
1018  else
1019  {
1020  config_warn("Tag '%s' at line %d of file '%s' has become obsolete.\n"
1021  " To avoid this warning please remove this line from your configuration "
1022  "file or upgrade it using \"doxygen -u\"\n", qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
1023  }
1024  ((ConfigObsolete*)option)->markAsPresent();
1025  if (((ConfigObsolete*)option)->orgType()==ConfigOption::O_List)
1026  {
1027  g_list = ((ConfigObsolete*)option)->valueListRef();
1028  g_list->clear();
1029  g_listStr="";
1030  BEGIN(GetStrList);
1031  }
1032  else
1033  {
1034  g_string = ((ConfigObsolete*)option)->valueStringRef();
1035  g_string->resize(0);
1036  BEGIN(GetString);
1037  }
1038  break;
1039  case ConfigOption::O_Disabled:
1040  if (g_configUpdate)
1041  {
1042  config_warn("Tag '%s' at line %d of file '%s' belongs to an option that was not enabled at compile time.\n"
1043  " This tag has been removed.\n", qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
1044  }
1045  else
1046  {
1047  config_warn("Tag '%s' at line %d of file '%s' belongs to an option that was not enabled at compile time.\n"
1048  " To avoid this warning please remove this line from your configuration "
1049  "file or upgrade it using \"doxygen -u\", or recompile doxygen with this feature enabled.\n", qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
1050  }
1051  BEGIN(SkipInvalid);
1052  break;
1053  }
1054  }
1055  }
1056 <Start>[a-z_A-Z][a-z_A-Z0-9]*[ \t]*"+=" { g_cmd=yytext;
1057  g_cmd=g_cmd.left(g_cmd.length()-2).stripWhiteSpace();
1058  ConfigOption *option = g_config->get(g_cmd);
1059  if (option==0) // oops not known
1060  {
1061  config_warn("ignoring unsupported tag '%s' at line %d, file %s\n",
1062  qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
1063  BEGIN(SkipInvalid);
1064  }
1065  else // known tag
1066  {
1067  option->setUserComment(g_config->takeUserComment());
1068  switch(option->kind())
1069  {
1070  case ConfigOption::O_Info:
1071  // shouldn't get here!
1072  BEGIN(SkipInvalid);
1073  break;
1074  case ConfigOption::O_List:
1075  g_list = ((ConfigList *)option)->valueRef();
1076  g_listStr="";
1077  BEGIN(GetStrList);
1078  break;
1079  case ConfigOption::O_Enum:
1080  case ConfigOption::O_String:
1081  case ConfigOption::O_Int:
1082  case ConfigOption::O_Bool:
1083  config_warn("operator += not supported for '%s'. Ignoring line at line %d, file %s\n",
1084  yytext,g_yyLineNr,qPrint(g_yyFileName));
1085  BEGIN(SkipInvalid);
1086  break;
1087  case ConfigOption::O_Obsolete:
1088  config_warn("Tag '%s' at line %d of file %s has become obsolete.\n"
1089  "To avoid this warning please update your configuration "
1090  "file using \"doxygen -u\"\n", qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
1091  if (((ConfigObsolete*)option)->orgType()==ConfigOption::O_List)
1092  {
1093  g_list = ((ConfigObsolete*)option)->valueListRef();
1094  g_listStr="";
1095  BEGIN(GetStrList);
1096  }
1097  else
1098  {
1099  BEGIN(SkipInvalid);
1100  }
1101  break;
1102  case ConfigOption::O_Disabled:
1103  config_warn("Tag '%s' at line %d of file %s belongs to an option that was not enabled at compile time.\n"
1104  "To avoid this warning please remove this line from your configuration "
1105  "file, upgrade it using \"doxygen -u\", or recompile doxygen with this feature enabled.\n", qPrint(g_cmd),g_yyLineNr,qPrint(g_yyFileName));
1106  BEGIN(SkipInvalid);
1107  break;
1108  }
1109  }
1110  }
1111 
1112  /*-------------- INCLUDE* ---------------*/
1113 
1114 <Start>"@INCLUDE_PATH"[ \t]*"=" { BEGIN(GetStrList); g_list=&g_includePathList; g_list->clear(); g_listStr=""; }
1115  /* include a g_config file */
1116 <Start>"@INCLUDE"[ \t]*"=" { BEGIN(Include);}
1117 <Include>([^ \"\t\r\n]+)|("\""[^\n\"]+"\"") {
1118  readIncludeFile(configStringRecode(yytext,g_encoding,"UTF-8"));
1119  BEGIN(Start);
1120  }
1121 <<EOF>> {
1122  //printf("End of include file\n");
1123  //printf("Include stack depth=%d\n",g_includeStack.count());
1124  if (g_includeStack.empty())
1125  {
1126  //printf("Terminating scanner!\n");
1127  yyterminate();
1128  }
1129  else
1130  {
1131  auto &fs=g_includeStack.back();
1132  fclose(fs->filePtr);
1133  YY_BUFFER_STATE oldBuf = YY_CURRENT_BUFFER;
1134  yy_switch_to_buffer( fs->oldState );
1135  yy_delete_buffer( oldBuf );
1136  g_yyLineNr=fs->lineNr;
1137  g_yyFileName=fs->fileName;
1138  g_includeStack.pop_back();
1139  }
1140  }
1141 
1142 <Start>[a-z_A-Z0-9]+ { config_warn("ignoring unknown tag '%s' at line %d, file %s\n",yytext,g_yyLineNr,qPrint(g_yyFileName)); }
1143  /*-------------- GetString ---------------*/
1144 
1145 <GetString>\n { processString();
1146  g_yyLineNr++; // end of string
1147  BEGIN(Start);
1148  }
1149 <GetString>\\[ \r\t]*\n { g_yyLineNr++; // line continuation
1150  *g_string+=' ';
1151  }
1152 <GetString>"\\" { // escape character
1153  *g_string+=yytext;
1154  }
1155 <GetString>[^\n\\]+ { // string part without escape characters
1156  *g_string+=yytext;
1157  }
1158 
1159  /*-------------- GetStrList --------------*/
1160 
1161 <GetStrList>\n { processList();
1162  g_yyLineNr++; // end of list
1163  BEGIN(Start);
1164  }
1165 <GetStrList>\\[ \r\t]*\n { g_yyLineNr++; // line continuation
1166  g_listStr+=' ';
1167  }
1168 <GetStrList>"\\" { // escape character
1169  g_listStr+=yytext;
1170  }
1171 <GetStrList>[^\n\\]+ { // string part without escape characters
1172  g_listStr+=yytext;
1173  }
1174 
1175  /*-------------- SkipInvalid --------------*/
1176 
1177 <SkipInvalid>\n { g_yyLineNr++; // end of list
1178  BEGIN(Start);
1179  }
1180 <SkipInvalid>\\[ \r\t]*\n { g_yyLineNr++; // line continuation
1181  }
1182 <SkipInvalid>"\\" { // escape character
1183  }
1184 <SkipInvalid>[^\n\\]+ { // string part without escape characters
1185  }
1186 
1187  /*-------------- fall through -------------*/
1188 
1189 <*>\\[ \r\t]*\n { g_yyLineNr++; }
1190 <*>[ \t\r]
1191 <*>\n { g_yyLineNr++ ; }
1192 <*>. { config_warn("ignoring unknown character '%c' at line %d, file %s\n",yytext[0],g_yyLineNr,qPrint(g_yyFileName)); }
1193 
1194 %%
1195 
1196 /*@ ----------------------------------------------------------------------------
1197  */
1198 
1199 void ConfigImpl::writeTemplate(TextStream &t,bool sl,bool upd)
1200 {
1201  /* print first lines of user comment that were at the beginning of the file, might have special meaning for editors */
1202  if (!m_startComment.isEmpty())
1203  {
1204  t << takeStartComment() << "\n";
1205  }
1206  t << "# Doxyfile " << getDoxygenVersion() << "\n\n";
1207  if (!sl)
1208  {
1209  t << convertToComment(m_header,"");
1210  }
1211  for (const auto &option : m_options)
1212  {
1213  option->writeTemplate(t,sl,upd);
1214  }
1215  /* print last lines of user comment that were at the end of the file */
1216  if (!m_userComment.isEmpty())
1217  {
1218  t << "\n";
1219  t << takeUserComment();
1220  }
1221 }
1222 
1223 void ConfigImpl::compareDoxyfile(TextStream &t)
1224 {
1225  t << "# Difference with default Doxyfile " << getFullVersion();
1226  t << "\n";
1227  for (const auto &option : m_options)
1228  {
1229  option->m_userComment = "";
1230  option->compareDoxyfile(t);
1231  }
1232 }
1233 
1234 void ConfigImpl::writeXMLDoxyfile(TextStream &t)
1235 {
1236  t << "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n";
1237  t << "<doxyfile xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"doxyfile.xsd\" version=\"" << getDoxygenVersion() << "\" xml:lang=\"" << theTranslator->trISOLang() << "\">\n";
1238  for (const auto &option : m_options)
1239  {
1240  option->writeXMLDoxyfile(t);
1241  }
1242  t << "</doxyfile>\n";
1243 }
1244 
1245 void ConfigImpl::convertStrToVal()
1246 {
1247  for (const auto &option : m_options)
1248  {
1249  option->convertStrToVal();
1250  }
1251 }
1252 void ConfigImpl::emptyValueToDefault()
1253 {
1254  for (const auto &option : m_options)
1255  {
1256  option->emptyValueToDefault();
1257  }
1258 }
1259 
1260 static void substEnvVarsInString(QCString &str)
1261 {
1262  if (str.isEmpty()) return;
1263  auto replace = [](const std::string &s, const reg::Ex &re) -> std::string
1264  {
1265  reg::Iterator it(s,re);
1266  reg::Iterator end;
1267  std::string result;
1268  size_t p = 0;
1269  for (; it!=end ; ++it)
1270  {
1271  const auto &match = *it;
1272  size_t i = match.position();
1273  size_t l = match.length();
1274  result+=s.substr(p,i-p);
1275  std::string matchContents = match[1].str();
1276  QCString env=Portable::getenv(matchContents.c_str()); // get content of $(..) match
1277  substEnvVarsInString(env); // recursively expand variables if needed.
1278  result+=env.str();
1279  p=i+l;
1280  }
1281  result+=s.substr(p);
1282  return result;
1283  };
1284 
1285  // match e.g. re1=$(HOME) but also re2=$(PROGRAMFILES(X86))
1286  static const reg::Ex re1(R"(\$\((\a[\w.-]*)\))");
1287  static const reg::Ex re2(R"(\$\((\a[\w.-]*\(\a[\w.-]*\))\))");
1288  str = QCString(replace(replace(str.str(),re1),re2)).stripWhiteSpace();
1289 }
1290 
1291 static void substEnvVarsInStrList(StringVector &sl)
1292 {
1293  StringVector results;
1294  for (const auto &s : sl)
1295  {
1296  QCString result = s.c_str();
1297  bool wasQuoted = (result.find(' ')!=-1) || (result.find('\t')!=-1) || (result.find('"')!=-1);
1298  // here we strip the quote again
1299  substEnvVarsInString(result);
1300 
1301  //printf("Result %s was quoted=%d\n",qPrint(result),wasQuoted);
1302 
1303  if (!wasQuoted) /* as a result of the expansion, a single string
1304  may have expanded into a list, which we'll
1305  add to sl. If the original string already
1306  contained multiple elements no further
1307  splitting is done to allow quoted items with spaces! */
1308  {
1309  int l=result.length();
1310  int i,p=0;
1311  // skip spaces
1312  // search for a "word"
1313  for (i=0;i<l;i++)
1314  {
1315  char c=0;
1316  // skip until start of new word
1317  while (i<l && ((c=result.at(i))==' ' || c=='\t')) i++;
1318  p=i; // p marks the start index of the word
1319  // skip until end of a word
1320  while (i<l && ((c=result.at(i))!=' ' && c!='\t' && c!='"')) i++;
1321  if (i<l) // not at the end of the string
1322  {
1323  if (c=='"') // word within quotes
1324  {
1325  p=i+1;
1326  for (i++;i<l;i++)
1327  {
1328  c=result.at(i);
1329  if (c=='"') // end quote
1330  {
1331  results.push_back(result.mid(p,i-p).str());
1332  p=i+1;
1333  break;
1334  }
1335  else if (c=='\\') // skip escaped stuff
1336  {
1337  i++;
1338  }
1339  }
1340  }
1341  else if (c==' ' || c=='\t') // separator
1342  {
1343  if (i>p) results.push_back(result.mid(p,i-p).str());
1344  p=i+1;
1345  }
1346  }
1347  }
1348  if (p!=l) // add the leftover as a string
1349  {
1350  results.push_back(result.right(l-p).str());
1351  }
1352  }
1353  else // just goto the next element in the list
1354  {
1355  if (!result.isEmpty()) results.push_back(result.str());
1356  }
1357  }
1358  sl = results;
1359 }
1360 
1361 void ConfigString::substEnvVars()
1362 {
1363  substEnvVarsInString(m_value);
1364 }
1365 
1366 void ConfigList::substEnvVars()
1367 {
1368  substEnvVarsInStrList(m_value);
1369 }
1370 
1371 void ConfigBool::substEnvVars()
1372 {
1373  substEnvVarsInString(m_valueString);
1374 }
1375 
1376 void ConfigInt::substEnvVars()
1377 {
1378  substEnvVarsInString(m_valueString);
1379 }
1380 
1381 void ConfigEnum::substEnvVars()
1382 {
1383  substEnvVarsInString(m_value);
1384 }
1385 
1386 //---------------------------------------------
1387 
1388 void ConfigImpl::substituteEnvironmentVars()
1389 {
1390  for (const auto &option : m_options)
1391  {
1392  option->substEnvVars();
1393  }
1394 }
1395 
1396 void ConfigImpl::init()
1397 {
1398  for (const auto &option : m_options)
1399  {
1400  option->init();
1401  }
1402 
1403  // sanity check if all depends relations are valid
1404  for (const auto &option : m_options)
1405  {
1406  QCString depName = option->dependsOn();
1407  if (!depName.isEmpty())
1408  {
1409  ConfigOption * opt = ConfigImpl::instance()->get(depName);
1410  if (opt==0)
1411  {
1412  config_term("Config option '%s' has invalid depends relation on unknown option '%s'\n",
1413  qPrint(option->name()),qPrint(depName));
1414  }
1415  }
1416  }
1417 }
1418 
1419 void ConfigImpl::create()
1420 {
1421  if (m_initialized) return;
1422  m_initialized = TRUE;
1423  addConfigOptions(this);
1424 }
1425 
1426 static QCString configFileToString(const QCString &name)
1427 {
1428  if (name.isEmpty()) return QCString();
1429 
1430  auto stream2string = [](std::istream &in) -> std::string
1431  {
1432  std::string ret;
1433  char buffer[4096];
1434  while (in.read(buffer, sizeof(buffer))) ret.append(buffer, sizeof(buffer));
1435  ret.append(buffer, static_cast<uint>(in.gcount()));
1436  if (!ret.empty() && ret[ret.length()-1]!='\n') ret+='\n'; // to help the scanner
1437  return ret;
1438  };
1439 
1440  if (name=="-") // read from stdin
1441  {
1442  // read contents from stdin into contents string
1443  return stream2string(std::cin);
1444  }
1445  else // read from file
1446  {
1447  std::ifstream f(name.str(),std::istream::in);
1448  if (!f.is_open())
1449  {
1450  config_term("file '%s' not found or could not be opened\n",qPrint(name));
1451  return "";
1452  }
1453  return stream2string(f);
1454  }
1455 }
1456 
1457 bool ConfigImpl::parseString(const QCString &fn,const QCString &str,bool update)
1458 {
1459  g_config = ConfigImpl::instance();
1460  g_inputString = str.data();
1461  g_inputPosition = 0;
1462  g_yyFileName = fn;
1463  g_yyLineNr = 1;
1464  g_includeStack.clear();
1465  configimplYYrestart( configimplYYin );
1466  BEGIN( Start );
1467  g_configUpdate = update;
1468  configimplYYlex();
1469  g_configUpdate = FALSE;
1470  g_inputString = 0;
1471  return TRUE;
1472 }
1473 
1474 bool ConfigImpl::parse(const QCString &fn,bool update)
1475 {
1476  int retval;
1477  g_encoding = "UTF-8";
1478  printlex(yy_flex_debug, TRUE, __FILE__, qPrint(fn));
1479  retval = parseString(fn,configFileToString(fn), update);
1480  printlex(yy_flex_debug, FALSE, __FILE__, qPrint(fn));
1481  return retval;
1482 }
1483 
1484 //----------------------------------------------------------------------
1485 
1486 static void cleanUpPaths(StringVector &str)
1487 {
1488  for (size_t i=0;i<str.size();i++)
1489  {
1490  std::string path = str[i];
1491  std::replace(path.begin(),path.end(),'\\','/');
1492  if ((path[0]!='/' && (path.size()<=2 || path[1]!=':')) || path[path.size()-1]!='/')
1493  {
1494  FileInfo fi(path);
1495  if (fi.exists() && fi.isDir())
1496  {
1497  path = fi.absFilePath();
1498  if (path[path.size()-1]!='/') path+='/';
1499  }
1500  }
1501  str[i]=path;
1502  }
1503 }
1504 
1505 static bool checkFileName(const QCString &s,const char *optionName)
1506 {
1507  QCString val = s.stripWhiteSpace().lower();
1508  if ((val=="yes" || val=="true" || val=="1" || val=="all") ||
1509  (val=="no" || val=="false" || val=="0" || val=="none"))
1510  {
1511  err("file name expected for option %s, got %s instead. Ignoring...\n",optionName,qPrint(s));
1512  return false;
1513  }
1514  return true;
1515 }
1516 
1517 #include "config.h"
1518 
1519 void Config::init()
1520 {
1521  ConfigImpl::instance()->init();
1522 }
1523 
1524 static void checkList(const StringVector &list,const char *name, bool equalRequired,bool valueRequired)
1525 {
1526  for (const auto &s: list)
1527  {
1528  QCString item=s.c_str();
1529  item=item.stripWhiteSpace();
1530  int i=item.find('=');
1531  if (i==-1 && equalRequired)
1532  {
1533  err("Illegal format for option %s, no equal sign ('=') specified for item '%s'\n",name,qPrint(item));
1534  }
1535  if (i!=-1)
1536  {
1537  QCString myName=item.left(i).stripWhiteSpace();
1538  if (myName.isEmpty())
1539  {
1540  err("Illegal format for option %s, no name specified for item '%s'\n",name,qPrint(item));
1541  }
1542  else if (valueRequired)
1543  {
1544  QCString myValue=item.right(item.length()-i-1).stripWhiteSpace();
1545  if (myValue.isEmpty())
1546  {
1547  err("Illegal format for option %s, no value specified for item '%s'\n",name,qPrint(item));
1548  }
1549  }
1550  }
1551  }
1552 }
1553 
1554 void Config::checkAndCorrect(bool quiet)
1555 {
1556  ConfigValues::instance().init();
1557 
1558  Config_updateBool(QUIET,quiet || Config_getBool(QUIET));
1559  //------------------------
1560  // check WARN_FORMAT
1561  QCString warnFormat = Config_getString(WARN_FORMAT);
1562  if (warnFormat.find("$file")==-1)
1563  {
1564  warn_uncond("warning format does not contain a $file tag!\n");
1565  }
1566  if (warnFormat.find("$line")==-1)
1567  {
1568  warn_uncond("warning format does not contain a $line tag!\n");
1569  }
1570  if (warnFormat.find("$text")==-1)
1571  {
1572  warn_uncond("warning format foes not contain a $text tag!\n");
1573  }
1574 
1575  //------------------------
1576  // check and correct PAPER_TYPE
1577  QCString paperType = Config_getEnumAsString(PAPER_TYPE);
1578  paperType=paperType.lower().stripWhiteSpace();
1579  if (paperType.isEmpty() || paperType=="a4wide")
1580  {
1581  // use a4
1582  Config_updateEnum(PAPER_TYPE,PAPER_TYPE_t::a4);
1583  }
1584  else if (paperType!="a4" && paperType!="letter" &&
1585  paperType!="legal" && paperType!="executive")
1586  {
1587  err("Unknown page type '%s' specified\n",qPrint(paperType));
1588  Config_updateEnum(PAPER_TYPE,PAPER_TYPE_t::a4);
1589  }
1590 
1591  //------------------------
1592  // check & correct STRIP_FROM_PATH
1593  StringVector stripFromPath = Config_getList(STRIP_FROM_PATH);
1594  if (stripFromPath.empty()) // by default use the current path
1595  {
1596  std::string p = Dir::currentDirPath()+"/";
1597  stripFromPath.push_back(p);
1598  }
1599  else
1600  {
1601  cleanUpPaths(stripFromPath);
1602  }
1603  Config_updateList(STRIP_FROM_PATH,stripFromPath);
1604 
1605  //------------------------
1606  // check & correct STRIP_FROM_INC_PATH
1607  StringVector stripFromIncPath = Config_getList(STRIP_FROM_INC_PATH);
1608  cleanUpPaths(stripFromIncPath);
1609  Config_updateList(STRIP_FROM_INC_PATH,stripFromIncPath);
1610 
1611  //------------------------
1612  // Test to see if HTML header is valid
1613  QCString headerFile = Config_getString(HTML_HEADER);
1614  if (!headerFile.isEmpty())
1615  {
1616  FileInfo fi(headerFile.str());
1617  if (!fi.exists())
1618  {
1619  config_term("tag HTML_HEADER: header file '%s' "
1620  "does not exist\n",qPrint(headerFile));
1621  }
1622  }
1623 
1624  //------------------------
1625  // Test to see if HTML footer is valid
1626  QCString footerFile = Config_getString(HTML_FOOTER);
1627  if (!footerFile.isEmpty())
1628  {
1629  FileInfo fi(footerFile.str());
1630  if (!fi.exists())
1631  {
1632  config_term("tag HTML_FOOTER: footer file '%s' "
1633  "does not exist\n",qPrint(footerFile));
1634  }
1635  }
1636 
1637  //------------------------
1638  // Test to see if MathJax code file is valid
1639  if (Config_getBool(USE_MATHJAX))
1640  {
1641  auto mathJaxFormat = Config_getEnum(MATHJAX_FORMAT);
1642  auto mathjaxVersion = Config_getEnum(MATHJAX_VERSION);
1643  if (mathjaxVersion == MATHJAX_VERSION_t::MathJax_2)
1644  {
1645  if (mathJaxFormat==MATHJAX_FORMAT_t::chtml)
1646  {
1647  Config_updateEnum(MATHJAX_FORMAT,MATHJAX_FORMAT_t::HTML_CSS);
1648  }
1649  }
1650  else
1651  {
1652  if (mathJaxFormat==MATHJAX_FORMAT_t::HTML_CSS || mathJaxFormat==MATHJAX_FORMAT_t::NativeMML)
1653  {
1654  Config_updateEnum(MATHJAX_FORMAT,MATHJAX_FORMAT_t::chtml);
1655  }
1656  }
1657 
1658  QCString mathJaxCodefile = Config_getString(MATHJAX_CODEFILE);
1659  if (!mathJaxCodefile.isEmpty())
1660  {
1661  FileInfo fi(mathJaxCodefile.str());
1662  if (!fi.exists())
1663  {
1664  config_term("tag MATHJAX_CODEFILE file '%s' "
1665  "does not exist\n",qPrint(mathJaxCodefile));
1666  }
1667  }
1668  QCString path = Config_getString(MATHJAX_RELPATH);
1669  if (path.isEmpty())
1670  {
1671  path = "https://cdn.jsdelivr.net/npm/mathjax@";
1672  switch (mathjaxVersion)
1673  {
1674  case MATHJAX_VERSION_t::MathJax_2: path += "2"; break;
1675  case MATHJAX_VERSION_t::MathJax_3: path += "3"; break;
1676  }
1677  }
1678 
1679  if (path.at(path.length()-1)!='/')
1680  {
1681  path+="/";
1682  }
1683  Config_updateString(MATHJAX_RELPATH,path);
1684  }
1685 
1686  //------------------------
1687  // Test to see if LaTeX header is valid
1688  QCString latexHeaderFile = Config_getString(LATEX_HEADER);
1689  if (!latexHeaderFile.isEmpty())
1690  {
1691  FileInfo fi(latexHeaderFile.str());
1692  if (!fi.exists())
1693  {
1694  config_term("tag LATEX_HEADER: header file '%s' "
1695  "does not exist\n",qPrint(latexHeaderFile));
1696  }
1697  }
1698 
1699  //------------------------
1700  // Test to see if LaTeX footer is valid
1701  QCString latexFooterFile = Config_getString(LATEX_FOOTER);
1702  if (!latexFooterFile.isEmpty())
1703  {
1704  FileInfo fi(latexFooterFile.str());
1705  if (!fi.exists())
1706  {
1707  config_term("tag LATEX_FOOTER: footer file '%s' "
1708  "does not exist\n",qPrint(latexFooterFile));
1709  }
1710  }
1711 
1712  //------------------------
1713  // check include path
1714  const StringVector &includePath = Config_getList(INCLUDE_PATH);
1715  for (const auto &s : includePath)
1716  {
1717  FileInfo fi(s);
1718  if (!fi.exists()) warn_uncond("tag INCLUDE_PATH: include path '%s' "
1719  "does not exist\n",s.c_str());
1720  }
1721 
1722  //------------------------
1723  // check PREDEFINED
1724  if (Config_getBool(ENABLE_PREPROCESSING))
1725  {
1726  const StringVector &predefList = Config_getList(PREDEFINED);
1727  for (const auto &s : predefList)
1728  {
1729  QCString predef=s.c_str();
1730  predef=predef.stripWhiteSpace();
1731  int i_equals=predef.find('=');
1732  int i_obrace=predef.find('(');
1733  if ((i_obrace==0) || (i_equals==0) || (i_equals==1 && predef.at(i_equals-1)==':'))
1734  {
1735  err("Illegal PREDEFINED format '%s', no define name specified\n",qPrint(predef));
1736  }
1737  }
1738  }
1739 
1740  //------------------------
1741  // check ALIASES
1742  const StringVector &aliasList = Config_getList(ALIASES);
1743  for (const auto &alias : aliasList)
1744  {
1745  // match aliases of the form re1='name=' and re2='name{2} ='
1746  static const reg::Ex re1(R"(^\a\w*\s*=)");
1747  static const reg::Ex re2(R"(^\a\w*{\d+}\s*=)");
1748  if (!reg::search(alias,re1) && !reg::search(alias,re2))
1749  {
1750  err("Illegal ALIASES format '%s'. Use \"name=value\" or \"name{n}=value\", where n is the number of arguments\n",
1751  alias.c_str());
1752  }
1753  }
1754 
1755  //------------------------
1756  // check EXTENSION_MAPPING
1757  checkList(Config_getList(EXTENSION_MAPPING),"EXTENSION_MAPPING",TRUE,TRUE);
1758 
1759  //------------------------
1760  // check FILTER_PATTERNS
1761  checkList(Config_getList(FILTER_PATTERNS),"FILTER_PATTERNS",TRUE,TRUE);
1762 
1763  //------------------------
1764  // check FILTER_SOURCE_PATTERNS
1765  checkList(Config_getList(FILTER_SOURCE_PATTERNS),"FILTER_SOURCE_PATTERNS",FALSE,FALSE);
1766 
1767  //------------------------
1768  // check TAGFILES
1769  checkList(Config_getList(TAGFILES),"TAGFILES",FALSE,TRUE);
1770 
1771  //------------------------
1772  // check EXTRA_SEARCH_MAPPINGS
1773  if (Config_getBool(SEARCHENGINE) && Config_getBool(GENERATE_HTML))
1774  {
1775  checkList(Config_getList(EXTRA_SEARCH_MAPPINGS),"EXTRA_SEARCH_MAPPING",TRUE,TRUE);
1776  }
1777 
1778  //------------------------
1779  // check if GENERATE_TREEVIEW and GENERATE_HTMLHELP are both enabled
1780  if (Config_getBool(GENERATE_TREEVIEW) && Config_getBool(GENERATE_HTMLHELP))
1781  {
1782  err("When enabling GENERATE_HTMLHELP the tree view (GENERATE_TREEVIEW) should be disabled. I'll do it for you.\n");
1783  Config_updateBool(GENERATE_TREEVIEW,FALSE);
1784  }
1785 
1786  //------------------------
1787  // check if GENERATE_HTMLHELP and HTML_FILE_EXTENSION is not .html
1788  if (Config_getString(HTML_FILE_EXTENSION)!=".html" && Config_getBool(GENERATE_HTMLHELP))
1789  {
1790  err("When enabling GENERATE_HTMLHELP the HTML_FILE_EXTENSION should be \".html\". I'll do it for you.\n");
1791  Config_updateString(HTML_FILE_EXTENSION,".html");
1792  }
1793 
1794  //------------------------
1795  // check if SEARCHENGINE and GENERATE_HTMLHELP are both enabled
1796  if (Config_getBool(SEARCHENGINE) && Config_getBool(GENERATE_HTMLHELP))
1797  {
1798  err("When enabling GENERATE_HTMLHELP the search engine (SEARCHENGINE) should be disabled. I'll do it for you.\n");
1799  Config_updateBool(SEARCHENGINE,FALSE);
1800  }
1801 
1802  //------------------------
1803  // check if SEPARATE_MEMBER_PAGES and INLINE_GROUPED_CLASSES are both enabled
1804  if (Config_getBool(SEPARATE_MEMBER_PAGES) && Config_getBool(INLINE_GROUPED_CLASSES))
1805  {
1806  err("When enabling INLINE_GROUPED_CLASSES the SEPARATE_MEMBER_PAGES option should be disabled. I'll do it for you.\n");
1807  Config_updateBool(SEPARATE_MEMBER_PAGES,FALSE);
1808  }
1809 
1810  //------------------------
1811  // correct DOT_FONTNAME if needed
1812  QCString dotFontName=Config_getString(DOT_FONTNAME);
1813  if (dotFontName=="FreeSans" || dotFontName=="FreeSans.ttf")
1814  {
1815  warn_uncond("doxygen no longer ships with the FreeSans font.\n"
1816  " You may want to clear or change DOT_FONTNAME.\n"
1817  " Otherwise you run the risk that the wrong font is being used for dot generated graphs.\n");
1818  }
1819 
1820  //------------------------
1821  // clip number of threads
1822  int dotNumThreads = Config_getInt(DOT_NUM_THREADS);
1823  if (dotNumThreads>32)
1824  {
1825  dotNumThreads=32;
1826  }
1827  else if (dotNumThreads<=0)
1828  {
1829  dotNumThreads=std::max(2u,std::thread::hardware_concurrency()+1);
1830  }
1831  Config_updateInt(DOT_NUM_THREADS,dotNumThreads);
1832 
1833  //------------------------
1834  // check dot path
1835  QCString dotPath = Config_getString(DOT_PATH);
1836  if (!dotPath.isEmpty())
1837  {
1838  FileInfo fi(dotPath.str());
1839  if (fi.exists() && fi.isFile()) // user specified path + exec
1840  {
1841  dotPath=fi.dirPath(TRUE)+"/";
1842  }
1843  else
1844  {
1845  QCString dotExe = dotPath+"/dot"+Portable::commandExtension();
1846  FileInfo dp(dotExe.str());
1847  if (!dp.exists() || !dp.isFile())
1848  {
1849  warn_uncond("the dot tool could not be found at %s\n",qPrint(dotPath));
1850  dotPath="";
1851  }
1852  else
1853  {
1854  dotPath=dp.dirPath(TRUE)+"/";
1855  }
1856  }
1857 #if defined(_WIN32) // convert slashes
1858  uint i=0,l=dotPath.length();
1859  for (i=0;i<l;i++) if (dotPath.at(i)=='/') dotPath.at(i)='\\';
1860 #endif
1861  Config_updateString(DOT_PATH,dotPath);
1862  }
1863 
1864  //------------------------
1865  // check plantuml path
1866  QCString plantumlJarPath = Config_getString(PLANTUML_JAR_PATH);
1867  if (!plantumlJarPath.isEmpty())
1868  {
1869  FileInfo pu(plantumlJarPath.str());
1870  if (pu.exists() && pu.isDir()) // PLANTUML_JAR_PATH is directory
1871  {
1872  QCString plantumlJar = plantumlJarPath+Portable::pathSeparator()+"plantuml.jar";
1873  FileInfo jar(plantumlJar.str());
1874  if (jar.exists() && jar.isFile())
1875  {
1876  plantumlJarPath = jar.dirPath(TRUE)+Portable::pathSeparator();
1877  }
1878  else
1879  {
1880  err("Jar file plantuml.jar not found at location "
1881  "specified via PLANTUML_JAR_PATH: '%s'\n",qPrint(plantumlJarPath));
1882  plantumlJarPath="";
1883  }
1884  }
1885  else if (pu.exists() && pu.isFile() && plantumlJarPath.right(4)==".jar") // PLANTUML_JAR_PATH is file
1886  {
1887  plantumlJarPath = pu.dirPath(TRUE)+Portable::pathSeparator();
1888  }
1889  else
1890  {
1891  err("path specified via PLANTUML_JAR_PATH does not exist or not a directory: %s\n",
1892  qPrint(plantumlJarPath));
1893  plantumlJarPath="";
1894  }
1895  Config_updateString(PLANTUML_JAR_PATH,plantumlJarPath);
1896  }
1897 
1898  //------------------------
1899  // check dia path
1900  QCString diaPath = Config_getString(DIA_PATH);
1901  if (!diaPath.isEmpty())
1902  {
1903  QCString diaExe = diaPath+"/dia"+Portable::commandExtension();
1904  FileInfo dp(diaExe.str());
1905  if (!dp.exists() || !dp.isFile())
1906  {
1907  warn_uncond("dia could not be found at %s\n",qPrint(diaPath));
1908  diaPath="";
1909  }
1910  else
1911  {
1912  diaPath=dp.dirPath(TRUE)+"/";
1913 #if defined(_WIN32) // convert slashes
1914  uint i=0,l=diaPath.length();
1915  for (i=0;i<l;i++) if (diaPath.at(i)=='/') diaPath.at(i)='\\';
1916 #endif
1917  }
1918  Config_updateString(DIA_PATH,diaPath);
1919  }
1920 
1921  //------------------------
1922  // check INPUT
1923  StringVector inputSources=Config_getList(INPUT);
1924  if (inputSources.empty())
1925  {
1926  // use current dir as the default
1927  inputSources.push_back(Dir::currentDirPath());
1928  }
1929  else
1930  {
1931  for (const auto &s : inputSources)
1932  {
1933  FileInfo fi(s.c_str());
1934  if (!fi.exists())
1935  {
1936  warn_uncond("tag INPUT: input source '%s' does not exist\n",s.c_str());
1937  }
1938  }
1939  }
1940  Config_updateList(INPUT,inputSources);
1941 
1942  //------------------------
1943  // if no output format is enabled, warn the user
1944  if (!Config_getBool(GENERATE_HTML) &&
1945  !Config_getBool(GENERATE_LATEX) &&
1946  !Config_getBool(GENERATE_MAN) &&
1947  !Config_getBool(GENERATE_RTF) &&
1948  !Config_getBool(GENERATE_XML) &&
1949  !Config_getBool(GENERATE_PERLMOD) &&
1950  !Config_getBool(GENERATE_RTF) &&
1951  !Config_getBool(GENERATE_DOCBOOK) &&
1952  !Config_getBool(GENERATE_AUTOGEN_DEF) &&
1953  Config_getString(GENERATE_TAGFILE).isEmpty()
1954  )
1955  {
1956  warn_uncond("No output formats selected! Set at least one of the main GENERATE_* options to YES.\n");
1957  }
1958 
1959  //------------------------
1960  // check HTMLHELP creation requirements
1961  if (!Config_getBool(GENERATE_HTML) &&
1962  Config_getBool(GENERATE_HTMLHELP))
1963  {
1964  warn_uncond("GENERATE_HTMLHELP=YES requires GENERATE_HTML=YES.\n");
1965  }
1966 
1967  //------------------------
1968  // check QHP creation requirements
1969  if (Config_getBool(GENERATE_QHP))
1970  {
1971  if (Config_getString(QHP_NAMESPACE).isEmpty())
1972  {
1973  err("GENERATE_QHP=YES requires QHP_NAMESPACE to be set. Using 'org.doxygen.doc' as default!.\n");
1974  Config_updateString(QHP_NAMESPACE,"org.doxygen.doc");
1975  }
1976 
1977  if (Config_getString(QHP_VIRTUAL_FOLDER).isEmpty())
1978  {
1979  err("GENERATE_QHP=YES requires QHP_VIRTUAL_FOLDER to be set. Using 'doc' as default!\n");
1980  Config_updateString(QHP_VIRTUAL_FOLDER,"doc");
1981  }
1982  }
1983 
1984  //------------------------
1985  if (Config_getBool(OPTIMIZE_OUTPUT_JAVA) && Config_getBool(INLINE_INFO))
1986  {
1987  // don't show inline info for Java output, since Java has no inline
1988  // concept.
1989  Config_updateBool(INLINE_INFO,FALSE);
1990  }
1991 
1992  //------------------------
1993  int depth = Config_getInt(MAX_DOT_GRAPH_DEPTH);
1994  if (depth==0)
1995  {
1996  Config_updateInt(MAX_DOT_GRAPH_DEPTH,1000);
1997  }
1998 
1999  //------------------------
2000  // some default settings for vhdl
2001  if (Config_getBool(OPTIMIZE_OUTPUT_VHDL) &&
2002  (Config_getBool(INLINE_INHERITED_MEMB) ||
2003  Config_getBool(INHERIT_DOCS) ||
2004  !Config_getBool(HIDE_SCOPE_NAMES) ||
2005  !Config_getBool(EXTRACT_PRIVATE) ||
2006  !Config_getBool(EXTRACT_PACKAGE)
2007  )
2008  )
2009  {
2010  bool b1 = Config_getBool(INLINE_INHERITED_MEMB);
2011  bool b2 = Config_getBool(INHERIT_DOCS);
2012  bool b3 = Config_getBool(HIDE_SCOPE_NAMES);
2013  bool b4 = Config_getBool(EXTRACT_PRIVATE);
2014  bool b5 = Config_getBool(SKIP_FUNCTION_MACROS);
2015  bool b6 = Config_getBool(EXTRACT_PACKAGE);
2016  const char *s1,*s2,*s3,*s4,*s5,*s6;
2017  if (b1) s1=" INLINE_INHERITED_MEMB = NO (was YES)\n"; else s1="";
2018  if (b2) s2=" INHERIT_DOCS = NO (was YES)\n"; else s2="";
2019  if (!b3) s3=" HIDE_SCOPE_NAMES = YES (was NO)\n"; else s3="";
2020  if (!b4) s4=" EXTRACT_PRIVATE = YES (was NO)\n"; else s4="";
2021  if (b5) s5=" ENABLE_PREPROCESSING = NO (was YES)\n"; else s5="";
2022  if (!b6) s6=" EXTRACT_PACKAGE = YES (was NO)\n"; else s6="";
2023 
2024 
2025  warn_uncond("enabling OPTIMIZE_OUTPUT_VHDL assumes the following settings:\n"
2026  "%s%s%s%s%s%s",s1,s2,s3,s4,s5,s6
2027  );
2028 
2029  Config_updateBool(INLINE_INHERITED_MEMB, FALSE);
2030  Config_updateBool(INHERIT_DOCS, FALSE);
2031  Config_updateBool(HIDE_SCOPE_NAMES, TRUE);
2032  Config_updateBool(EXTRACT_PRIVATE, TRUE);
2033  Config_updateBool(ENABLE_PREPROCESSING, FALSE);
2034  Config_updateBool(EXTRACT_PACKAGE, TRUE);
2035  }
2036 
2037  if (!checkFileName(Config_getString(GENERATE_TAGFILE),"GENERATE_TAGFILE"))
2038  {
2039  Config_updateString(GENERATE_TAGFILE,"");
2040  }
2041 
2042 #if 0 // TODO: this breaks test 25; SOURCEBROWSER = NO and SOURCE_TOOLTIPS = YES.
2043  // So this and other regressions should be analysed and fixed before this can be enabled
2044  // disable any boolean options that depend on disabled options
2045  for (const auto &option : m_options)
2046  {
2047  QCString depName = option->dependsOn(); // option has a dependency
2048  if (!depName.isEmpty())
2049  {
2050  ConfigOption * dep = Config::instance()->get(depName);
2051  if (dep->kind()==ConfigOption::O_Bool &&
2052  ConfigImpl_getBool("depName")==FALSE) // dependent option is disabled
2053  {
2054  if (option->kind()==ConfigOption::O_Bool)
2055  {
2056  printf("disabling option %s\n",qPrint(option->name()));
2057  ConfigImpl_getBool("option->name("))=FALSE; // also disable this option
2058  }
2059  }
2060  }
2061  }
2062 #endif
2063 
2064 }
2065 
2066 void Config::updateObsolete()
2067 {
2068  //------------------------
2069  // check for presence of obsolete CLASS_DIAGRAM option and correct CLASS_GRAPH if needed
2070  ConfigOption *classDiagramsOpt = ConfigImpl::instance()->get("CLASS_DIAGRAMS");
2071  ConfigOption *haveDotOpt = ConfigImpl::instance()->get("HAVE_DOT");
2072  ConfigOption *classGraphOpt = ConfigImpl::instance()->get("CLASS_GRAPH");
2073  if (classDiagramsOpt && classDiagramsOpt->kind()==ConfigOption::O_Obsolete &&
2074  haveDotOpt && classGraphOpt)
2075  {
2076  ConfigObsolete *classDiagramsOpt_ = dynamic_cast<ConfigObsolete*>(classDiagramsOpt);
2077  ConfigBool *haveDotOpt_ = dynamic_cast<ConfigBool*>(haveDotOpt);
2078  ConfigEnum *classGraphOpt_ = dynamic_cast<ConfigEnum*>(classGraphOpt);
2079  if (classDiagramsOpt_->isPresent() && classDiagramsOpt_->orgType()==ConfigOption::O_Bool)
2080  {
2081  QCString classDiagramValue = *classDiagramsOpt_->valueStringRef();
2082  QCString haveDotValue = *haveDotOpt_->valueStringRef();
2083  QCString &classGraphValue = *classGraphOpt_->valueRef();
2084  bool isValid1=true, isValid2=true;
2085  bool bClassDiagrams = convertStringToBool(classDiagramValue,isValid1);
2086  bool bHaveDot = haveDotValue.isEmpty() ? false : convertStringToBool(haveDotValue, isValid2);
2087  if (isValid1 && isValid2 && !bClassDiagrams && !bHaveDot && classGraphValue.lower()=="yes")
2088  {
2089  warn_uncond("Changing CLASS_GRAPH option to TEXT because obsolete option CLASS_DIAGRAM was found and set to NO.\n");
2090  classGraphValue="TEXT";
2091  }
2092  }
2093  }
2094 }
2095 
2096 void Config::writeTemplate(TextStream &t,bool shortList,bool update)
2097 {
2098  ConfigImpl::instance()->writeTemplate(t,shortList,update);
2099 }
2100 
2101 void Config::compareDoxyfile(TextStream &t)
2102 {
2103  postProcess(FALSE, TRUE);
2104  ConfigImpl::instance()->compareDoxyfile(t);
2105 }
2106 
2107 void Config::writeXMLDoxyfile(TextStream &t)
2108 {
2109  ConfigImpl::instance()->writeXMLDoxyfile(t);
2110 }
2111 
2112 bool Config::parse(const QCString &fileName,bool update)
2113 {
2114  bool parseRes = ConfigImpl::instance()->parse(fileName,update);
2115  if (!parseRes) return parseRes;
2116 
2117  // Internally we use the default format UTF-8 and
2118  // when updating etc. the output is in this format as well and not in the read format
2119  ConfigString *option = (ConfigString*)g_config->get("DOXYFILE_ENCODING");
2120  option->init();
2121 
2122  return parseRes;
2123 }
2124 
2125 void Config::postProcess(bool clearHeaderAndFooter, bool compare)
2126 {
2127  ConfigImpl::instance()->substituteEnvironmentVars();
2128  if (!compare)ConfigImpl::instance()->emptyValueToDefault();
2129  ConfigImpl::instance()->convertStrToVal();
2130 
2131  // avoid bootstrapping issues when the g_config file already
2132  // refers to the files that we are supposed to parse.
2133  if (clearHeaderAndFooter)
2134  {
2135  Config_updateString(HTML_HEADER ,"");
2136  Config_updateString(HTML_FOOTER ,"");
2137  Config_updateString(LATEX_HEADER,"");
2138  Config_updateString(LATEX_FOOTER,"");
2139  }
2140 }
2141 
2142 void Config::deinit()
2143 {
2144  ConfigImpl::instance()->deleteInstance();
2145 }
2146 
2147 #if USE_STATE2STRING
2148 #include "configimpl.l.h"
2149 #endif