1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/fileconf.cpp
3 // Purpose:     implementation of wxFileConfig derivation of wxConfig
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     07.04.98 (adapted from appconf.cpp)
7 // RCS-ID:      $Id: fileconf.cpp 50711 2007-12-15 02:57:58Z VZ $
8 // Copyright:   (c) 1997 Karsten Ballueder   &  Vadim Zeitlin
9 //                       Ballueder@usa.net     <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence:     wxWindows licence
11 ///////////////////////////////////////////////////////////////////////////////
12 
13 // ----------------------------------------------------------------------------
14 // headers
15 // ----------------------------------------------------------------------------
16 
17 // For compilers that support precompilation, includes "wx.h".
18 #include  "wx/wxprec.h"
19 
20 #ifdef    __BORLANDC__
21     #pragma hdrstop
22 #endif  //__BORLANDC__
23 
24 #if wxUSE_CONFIG && wxUSE_FILECONFIG
25 
26 #ifndef   WX_PRECOMP
27     #include  "wx/dynarray.h"
28     #include  "wx/string.h"
29     #include  "wx/intl.h"
30     #include  "wx/log.h"
31     #include  "wx/app.h"
32     #include  "wx/utils.h"    // for wxGetHomeDir
33     #if wxUSE_STREAMS
34         #include  "wx/stream.h"
35     #endif // wxUSE_STREAMS
36 #endif  //WX_PRECOMP
37 
38 #include  "wx/file.h"
39 #include  "wx/textfile.h"
40 #include  "wx/memtext.h"
41 #include  "wx/config.h"
42 #include  "wx/fileconf.h"
43 #include  "wx/filefn.h"
44 
45 #if defined(__WXMAC__)
46     #include  "wx/mac/private.h"  // includes mac headers
47     #include  "wx/filename.h"     // for MacSetTypeAndCreator
48 #endif
49 
50 #if defined(__WXMSW__)
51     #include "wx/msw/private.h"
52 #endif  //windows.h
53 #if defined(__WXPM__)
54     #define INCL_DOS
55     #include <os2.h>
56 #endif
57 
58 #include  <stdlib.h>
59 #include  <ctype.h>
60 
61 // ----------------------------------------------------------------------------
62 // macros
63 // ----------------------------------------------------------------------------
64 #define CONST_CAST ((wxFileConfig *)this)->
65 
66 // ----------------------------------------------------------------------------
67 // constants
68 // ----------------------------------------------------------------------------
69 
70 #ifndef MAX_PATH
71     #define MAX_PATH 512
72 #endif
73 
74 #define FILECONF_TRACE_MASK _T("fileconf")
75 
76 // ----------------------------------------------------------------------------
77 // global functions declarations
78 // ----------------------------------------------------------------------------
79 
80 // compare functions for sorting the arrays
81 static int LINKAGEMODE CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2);
82 static int LINKAGEMODE CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2);
83 
84 // filter strings
85 static wxString FilterInValue(const wxString& str);
86 static wxString FilterOutValue(const wxString& str);
87 
88 static wxString FilterInEntryName(const wxString& str);
89 static wxString FilterOutEntryName(const wxString& str);
90 
91 // get the name to use in wxFileConfig ctor
92 static wxString GetAppName(const wxString& appname);
93 
94 // ============================================================================
95 // private classes
96 // ============================================================================
97 
98 // ----------------------------------------------------------------------------
99 // "template" array types
100 // ----------------------------------------------------------------------------
101 
102 #ifdef WXMAKINGDLL_BASE
103     WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigEntry *, ArrayEntries,
104                                          WXDLLIMPEXP_BASE);
105     WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigGroup *, ArrayGroups,
106                                          WXDLLIMPEXP_BASE);
107 #else
108     WX_DEFINE_SORTED_ARRAY(wxFileConfigEntry *, ArrayEntries);
109     WX_DEFINE_SORTED_ARRAY(wxFileConfigGroup *, ArrayGroups);
110 #endif
111 
112 // ----------------------------------------------------------------------------
113 // wxFileConfigLineList
114 // ----------------------------------------------------------------------------
115 
116 // we store all lines of the local config file as a linked list in memory
117 class wxFileConfigLineList
118 {
119 public:
SetNext(wxFileConfigLineList * pNext)120   void SetNext(wxFileConfigLineList *pNext)  { m_pNext = pNext; }
SetPrev(wxFileConfigLineList * pPrev)121   void SetPrev(wxFileConfigLineList *pPrev)  { m_pPrev = pPrev; }
122 
123   // ctor
wxFileConfigLineList(const wxString & str,wxFileConfigLineList * pNext=NULL)124   wxFileConfigLineList(const wxString& str,
125                        wxFileConfigLineList *pNext = NULL) : m_strLine(str)
126     { SetNext(pNext); SetPrev(NULL); }
127 
128   // next/prev nodes in the linked list
Next() const129   wxFileConfigLineList *Next() const { return m_pNext;  }
Prev() const130   wxFileConfigLineList *Prev() const { return m_pPrev;  }
131 
132   // get/change lines text
SetText(const wxString & str)133   void SetText(const wxString& str) { m_strLine = str;  }
Text() const134   const wxString& Text() const { return m_strLine; }
135 
136 private:
137   wxString  m_strLine;                  // line contents
138   wxFileConfigLineList *m_pNext,        // next node
139                        *m_pPrev;        // previous one
140 
141     DECLARE_NO_COPY_CLASS(wxFileConfigLineList)
142 };
143 
144 // ----------------------------------------------------------------------------
145 // wxFileConfigEntry: a name/value pair
146 // ----------------------------------------------------------------------------
147 
148 class wxFileConfigEntry
149 {
150 private:
151   wxFileConfigGroup *m_pParent; // group that contains us
152 
153   wxString      m_strName,      // entry name
154                 m_strValue;     //       value
155   bool          m_bImmutable:1, // can be overriden locally?
156                 m_bHasValue:1;  // set after first call to SetValue()
157 
158   int           m_nLine;        // used if m_pLine == NULL only
159 
160   // pointer to our line in the linked list or NULL if it was found in global
161   // file (which we don't modify)
162   wxFileConfigLineList *m_pLine;
163 
164 public:
165   wxFileConfigEntry(wxFileConfigGroup *pParent,
166                     const wxString& strName, int nLine);
167 
168   // simple accessors
Name() const169   const wxString& Name()        const { return m_strName;    }
Value() const170   const wxString& Value()       const { return m_strValue;   }
Group() const171   wxFileConfigGroup *Group()    const { return m_pParent;    }
IsImmutable() const172   bool            IsImmutable() const { return m_bImmutable; }
IsLocal() const173   bool            IsLocal()     const { return m_pLine != 0; }
Line() const174   int             Line()        const { return m_nLine;      }
175   wxFileConfigLineList *
GetLine() const176                   GetLine()     const { return m_pLine;      }
177 
178   // modify entry attributes
179   void SetValue(const wxString& strValue, bool bUser = true);
180   void SetLine(wxFileConfigLineList *pLine);
181 
182     DECLARE_NO_COPY_CLASS(wxFileConfigEntry)
183 };
184 
185 // ----------------------------------------------------------------------------
186 // wxFileConfigGroup: container of entries and other groups
187 // ----------------------------------------------------------------------------
188 
189 class wxFileConfigGroup
190 {
191 private:
192   wxFileConfig *m_pConfig;          // config object we belong to
193   wxFileConfigGroup  *m_pParent;    // parent group (NULL for root group)
194   ArrayEntries  m_aEntries;         // entries in this group
195   ArrayGroups   m_aSubgroups;       // subgroups
196   wxString      m_strName;          // group's name
197   wxFileConfigLineList *m_pLine;    // pointer to our line in the linked list
198   wxFileConfigEntry *m_pLastEntry;  // last entry/subgroup of this group in the
199   wxFileConfigGroup *m_pLastGroup;  // local file (we insert new ones after it)
200 
201   // DeleteSubgroupByName helper
202   bool DeleteSubgroup(wxFileConfigGroup *pGroup);
203 
204   // used by Rename()
205   void UpdateGroupAndSubgroupsLines();
206 
207 public:
208   // ctor
209   wxFileConfigGroup(wxFileConfigGroup *pParent, const wxString& strName, wxFileConfig *);
210 
211   // dtor deletes all entries and subgroups also
212   ~wxFileConfigGroup();
213 
214   // simple accessors
Name() const215   const wxString& Name()    const { return m_strName; }
Parent() const216   wxFileConfigGroup    *Parent()  const { return m_pParent; }
Config() const217   wxFileConfig   *Config()  const { return m_pConfig; }
218 
Entries() const219   const ArrayEntries& Entries() const { return m_aEntries;   }
Groups() const220   const ArrayGroups&  Groups()  const { return m_aSubgroups; }
IsEmpty() const221   bool  IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }
222 
223   // find entry/subgroup (NULL if not found)
224   wxFileConfigGroup *FindSubgroup(const wxChar *szName) const;
225   wxFileConfigEntry *FindEntry   (const wxChar *szName) const;
226 
227   // delete entry/subgroup, return false if doesn't exist
228   bool DeleteSubgroupByName(const wxChar *szName);
229   bool DeleteEntry(const wxChar *szName);
230 
231   // create new entry/subgroup returning pointer to newly created element
232   wxFileConfigGroup *AddSubgroup(const wxString& strName);
233   wxFileConfigEntry *AddEntry   (const wxString& strName, int nLine = wxNOT_FOUND);
234 
235   void SetLine(wxFileConfigLineList *pLine);
236 
237   // rename: no checks are done to ensure that the name is unique!
238   void Rename(const wxString& newName);
239 
240   //
241   wxString GetFullName() const;
242 
243   // get the last line belonging to an entry/subgroup of this group
244   wxFileConfigLineList *GetGroupLine();     // line which contains [group]
245                                             // may be NULL for "/" only
246   wxFileConfigLineList *GetLastEntryLine(); // after which our subgroups start
247   wxFileConfigLineList *GetLastGroupLine(); // after which the next group starts
248 
249   // called by entries/subgroups when they're created/deleted
250   void SetLastEntry(wxFileConfigEntry *pEntry);
SetLastGroup(wxFileConfigGroup * pGroup)251   void SetLastGroup(wxFileConfigGroup *pGroup)
252     { m_pLastGroup = pGroup; }
253 
254   DECLARE_NO_COPY_CLASS(wxFileConfigGroup)
255 };
256 
257 // ============================================================================
258 // implementation
259 // ============================================================================
260 
261 // ----------------------------------------------------------------------------
262 // static functions
263 // ----------------------------------------------------------------------------
GetGlobalDir()264 wxString wxFileConfig::GetGlobalDir()
265 {
266   wxString strDir;
267 
268 #ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined
269     strDir = wxT("sys$manager:");
270 #elif defined(__WXMAC__)
271     strDir = wxMacFindFolder(  (short) kOnSystemDisk, kPreferencesFolderType, kDontCreateFolder ) ;
272 #elif defined( __UNIX__ )
273     strDir = wxT("/etc/");
274 #elif defined(__OS2__)
275     ULONG aulSysInfo[QSV_MAX] = {0};
276     UINT drive;
277     APIRET rc;
278 
279     rc = DosQuerySysInfo( 1L, QSV_MAX, (PVOID)aulSysInfo, sizeof(ULONG)*QSV_MAX);
280     if (rc == 0)
281     {
282         drive = aulSysInfo[QSV_BOOT_DRIVE - 1];
283         strDir.Printf(wxT("%c:\\OS2\\"), 'A'+drive-1);
284     }
285 #elif defined(__WXSTUBS__)
286     wxFAIL_MSG( wxT("TODO") );
287 #elif defined(__DOS__)
288     // There's no such thing as global cfg dir in MS-DOS, let's return
289     // current directory (FIXME_MGL?)
290     strDir = wxT(".\\");
291 #elif defined(__WXWINCE__)
292     strDir = wxT("\\Windows\\");
293 #else // Windows
294 
295     wxChar szWinDir[MAX_PATH];
296     ::GetWindowsDirectory(szWinDir, MAX_PATH);
297 
298     strDir = szWinDir;
299     strDir << wxT('\\');
300 #endif // Unix/Windows
301 
302     return strDir;
303 }
304 
GetLocalDir()305 wxString wxFileConfig::GetLocalDir()
306 {
307     wxString strDir;
308 
309 #if defined(__WXMAC__) || defined(__DOS__)
310     // no local dir concept on Mac OS 9 or MS-DOS
311     strDir << GetGlobalDir() ;
312 #else
313     wxGetHomeDir(&strDir);
314 
315     #ifdef  __UNIX__
316         if (
317             (strDir.Last() != wxT('/'))
318         #ifdef __VMS
319             && (strDir.Last() != wxT(']'))
320         #endif
321             )
322             strDir << wxT('/');
323     #else
324         if (strDir.Last() != wxT('\\'))
325             strDir << wxT('\\');
326     #endif
327 #endif
328 
329     return strDir;
330 }
331 
GetGlobalFileName(const wxChar * szFile)332 wxString wxFileConfig::GetGlobalFileName(const wxChar *szFile)
333 {
334     wxString str = GetGlobalDir();
335     str << szFile;
336 
337     if ( wxStrchr(szFile, wxT('.')) == NULL )
338 #if defined( __WXMAC__ )
339         str << wxT(" Preferences") ;
340 #elif defined( __UNIX__ )
341         str << wxT(".conf");
342 #else   // Windows
343         str << wxT(".ini");
344 #endif  // UNIX/Win
345 
346     return str;
347 }
348 
GetLocalFileName(const wxChar * szFile)349 wxString wxFileConfig::GetLocalFileName(const wxChar *szFile)
350 {
351 #ifdef __VMS__
352     // On VMS I saw the problem that the home directory was appended
353     // twice for the configuration file. Does that also happen for
354     // other platforms?
355     wxString str = wxT( '.' );
356 #else
357     wxString str = GetLocalDir();
358 #endif
359 
360 #if defined( __UNIX__ ) && !defined( __VMS ) && !defined( __WXMAC__ )
361     str << wxT('.');
362 #endif
363 
364     str << szFile;
365 
366 #if defined(__WINDOWS__) || defined(__DOS__)
367     if ( wxStrchr(szFile, wxT('.')) == NULL )
368         str << wxT(".ini");
369 #endif
370 
371 #ifdef __WXMAC__
372     str << wxT(" Preferences") ;
373 #endif
374 
375     return str;
376 }
377 
378 // ----------------------------------------------------------------------------
379 // ctor
380 // ----------------------------------------------------------------------------
381 
Init()382 void wxFileConfig::Init()
383 {
384     m_pCurrentGroup =
385     m_pRootGroup    = new wxFileConfigGroup(NULL, wxEmptyString, this);
386 
387     m_linesHead =
388     m_linesTail = NULL;
389 
390     // It's not an error if (one of the) file(s) doesn't exist.
391 
392     // parse the global file
393     if ( !m_strGlobalFile.empty() && wxFile::Exists(m_strGlobalFile) )
394     {
395         wxTextFile fileGlobal(m_strGlobalFile);
396 
397         if ( fileGlobal.Open(*m_conv/*ignored in ANSI build*/) )
398         {
399             Parse(fileGlobal, false /* global */);
400             SetRootPath();
401         }
402         else
403         {
404             wxLogWarning(_("can't open global configuration file '%s'."), m_strGlobalFile.c_str());
405         }
406     }
407 
408     // parse the local file
409     if ( !m_strLocalFile.empty() && wxFile::Exists(m_strLocalFile) )
410     {
411         wxTextFile fileLocal(m_strLocalFile);
412         if ( fileLocal.Open(*m_conv/*ignored in ANSI build*/) )
413         {
414             Parse(fileLocal, true /* local */);
415             SetRootPath();
416         }
417         else
418         {
419             wxLogWarning(_("can't open user configuration file '%s'."),  m_strLocalFile.c_str() );
420         }
421     }
422 
423     m_isDirty = false;
424 }
425 
426 // constructor supports creation of wxFileConfig objects of any type
wxFileConfig(const wxString & appName,const wxString & vendorName,const wxString & strLocal,const wxString & strGlobal,long style,const wxMBConv & conv)427 wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName,
428                            const wxString& strLocal, const wxString& strGlobal,
429                            long style,
430                            const wxMBConv& conv)
431             : wxConfigBase(::GetAppName(appName), vendorName,
432                            strLocal, strGlobal,
433                            style),
434               m_strLocalFile(strLocal), m_strGlobalFile(strGlobal),
435               m_conv(conv.Clone())
436 {
437     // Make up names for files if empty
438     if ( m_strLocalFile.empty() && (style & wxCONFIG_USE_LOCAL_FILE) )
439     {
440         m_strLocalFile = GetLocalFileName(GetAppName());
441 #if defined(__UNIX__) && !defined(__VMS)
442         if ( style & wxCONFIG_USE_SUBDIR )
443             m_strLocalFile << wxFILE_SEP_PATH << GetAppName() << _T(".conf");
444 #endif
445     }
446 
447     if ( m_strGlobalFile.empty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
448         m_strGlobalFile = GetGlobalFileName(GetAppName());
449 
450     // Check if styles are not supplied, but filenames are, in which case
451     // add the correct styles.
452     if ( !m_strLocalFile.empty() )
453         SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
454 
455     if ( !m_strGlobalFile.empty() )
456         SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
457 
458     // if the path is not absolute, prepend the standard directory to it
459     // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
460     if ( !(style & wxCONFIG_USE_RELATIVE_PATH) )
461     {
462         if ( !m_strLocalFile.empty() && !wxIsAbsolutePath(m_strLocalFile) )
463         {
464             const wxString strLocalOrig = m_strLocalFile;
465             m_strLocalFile = GetLocalDir();
466             m_strLocalFile << strLocalOrig;
467         }
468 
469         if ( !m_strGlobalFile.empty() && !wxIsAbsolutePath(m_strGlobalFile) )
470         {
471             const wxString strGlobalOrig = m_strGlobalFile;
472             m_strGlobalFile = GetGlobalDir();
473             m_strGlobalFile << strGlobalOrig;
474         }
475     }
476 
477     SetUmask(-1);
478 
479     Init();
480 }
481 
482 #if wxUSE_STREAMS
483 
wxFileConfig(wxInputStream & inStream,const wxMBConv & conv)484 wxFileConfig::wxFileConfig(wxInputStream &inStream, const wxMBConv& conv)
485             : m_conv(conv.Clone())
486 {
487     // always local_file when this constructor is called (?)
488     SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
489 
490     m_pCurrentGroup =
491     m_pRootGroup    = new wxFileConfigGroup(NULL, wxEmptyString, this);
492 
493     m_linesHead =
494     m_linesTail = NULL;
495 
496     // read the entire stream contents in memory
497     wxString str;
498     {
499         static const size_t chunkLen = 1024;
500 
501         wxMemoryBuffer buf(chunkLen);
502         do
503         {
504             inStream.Read(buf.GetAppendBuf(chunkLen), chunkLen);
505             buf.UngetAppendBuf(inStream.LastRead());
506 
507             const wxStreamError err = inStream.GetLastError();
508 
509             if ( err != wxSTREAM_NO_ERROR && err != wxSTREAM_EOF )
510             {
511                 wxLogError(_("Error reading config options."));
512                 break;
513             }
514         }
515         while ( !inStream.Eof() );
516 
517 #if wxUSE_UNICODE
518         size_t len;
519         str = conv.cMB2WC((char *)buf.GetData(), buf.GetDataLen(), &len);
520         if ( !len && buf.GetDataLen() )
521         {
522             wxLogError(_("Failed to read config options."));
523         }
524 #else // !wxUSE_UNICODE
525         // no need for conversion
526         str.assign((char *)buf.GetData(), buf.GetDataLen());
527 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
528     }
529 
530 
531     // translate everything to the current (platform-dependent) line
532     // termination character
533     str = wxTextBuffer::Translate(str);
534 
535     wxMemoryText memText;
536 
537     // Now we can add the text to the memory text. To do this we extract line
538     // by line from the translated string, until we've reached the end.
539     //
540     // VZ: all this is horribly inefficient, we should do the translation on
541     //     the fly in one pass saving both memory and time (TODO)
542 
543     const wxChar *pEOL = wxTextBuffer::GetEOL(wxTextBuffer::typeDefault);
544     const size_t EOLLen = wxStrlen(pEOL);
545 
546     int posLineStart = str.Find(pEOL);
547     while ( posLineStart != -1 )
548     {
549         wxString line(str.Left(posLineStart));
550 
551         memText.AddLine(line);
552 
553         str = str.Mid(posLineStart + EOLLen);
554 
555         posLineStart = str.Find(pEOL);
556     }
557 
558     // also add whatever we have left in the translated string.
559     if ( !str.empty() )
560         memText.AddLine(str);
561 
562     // Finally we can parse it all.
563     Parse(memText, true /* local */);
564 
565     SetRootPath();
566     ResetDirty();
567 }
568 
569 #endif // wxUSE_STREAMS
570 
CleanUp()571 void wxFileConfig::CleanUp()
572 {
573     delete m_pRootGroup;
574 
575     wxFileConfigLineList *pCur = m_linesHead;
576     while ( pCur != NULL ) {
577         wxFileConfigLineList *pNext = pCur->Next();
578         delete pCur;
579         pCur = pNext;
580     }
581 }
582 
~wxFileConfig()583 wxFileConfig::~wxFileConfig()
584 {
585     Flush();
586 
587     CleanUp();
588 
589     delete m_conv;
590 }
591 
592 // ----------------------------------------------------------------------------
593 // parse a config file
594 // ----------------------------------------------------------------------------
595 
Parse(const wxTextBuffer & buffer,bool bLocal)596 void wxFileConfig::Parse(const wxTextBuffer& buffer, bool bLocal)
597 {
598   const wxChar *pStart;
599   const wxChar *pEnd;
600   wxString strLine;
601 
602   size_t nLineCount = buffer.GetLineCount();
603 
604   for ( size_t n = 0; n < nLineCount; n++ )
605   {
606     strLine = buffer[n];
607 
608     // add the line to linked list
609     if ( bLocal )
610       LineListAppend(strLine);
611 
612 
613     // skip leading spaces
614     for ( pStart = strLine; wxIsspace(*pStart); pStart++ )
615       ;
616 
617     // skip blank/comment lines
618     if ( *pStart == wxT('\0')|| *pStart == wxT(';') || *pStart == wxT('#') )
619       continue;
620 
621     if ( *pStart == wxT('[') ) {          // a new group
622       pEnd = pStart;
623 
624       while ( *++pEnd != wxT(']') ) {
625         if ( *pEnd == wxT('\\') ) {
626             // the next char is escaped, so skip it even if it is ']'
627             pEnd++;
628         }
629 
630         if ( *pEnd == wxT('\n') || *pEnd == wxT('\0') ) {
631             // we reached the end of line, break out of the loop
632             break;
633         }
634       }
635 
636       if ( *pEnd != wxT(']') ) {
637         wxLogError(_("file '%s': unexpected character %c at line %d."),
638                    buffer.GetName(), *pEnd, n + 1);
639         continue; // skip this line
640       }
641 
642       // group name here is always considered as abs path
643       wxString strGroup;
644       pStart++;
645       strGroup << wxCONFIG_PATH_SEPARATOR
646                << FilterInEntryName(wxString(pStart, pEnd - pStart));
647 
648       // will create it if doesn't yet exist
649       SetPath(strGroup);
650 
651       if ( bLocal )
652       {
653         if ( m_pCurrentGroup->Parent() )
654           m_pCurrentGroup->Parent()->SetLastGroup(m_pCurrentGroup);
655         m_pCurrentGroup->SetLine(m_linesTail);
656       }
657 
658       // check that there is nothing except comments left on this line
659       bool bCont = true;
660       while ( *++pEnd != wxT('\0') && bCont ) {
661         switch ( *pEnd ) {
662           case wxT('#'):
663           case wxT(';'):
664             bCont = false;
665             break;
666 
667           case wxT(' '):
668           case wxT('\t'):
669             // ignore whitespace ('\n' impossible here)
670             break;
671 
672           default:
673             wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
674                          buffer.GetName(), n + 1, pEnd);
675             bCont = false;
676         }
677       }
678     }
679     else {                        // a key
680       pEnd = pStart;
681       while ( *pEnd && *pEnd != wxT('=') /* && !wxIsspace(*pEnd)*/ ) {
682         if ( *pEnd == wxT('\\') ) {
683           // next character may be space or not - still take it because it's
684           // quoted (unless there is nothing)
685           pEnd++;
686           if ( !*pEnd ) {
687             // the error message will be given below anyhow
688             break;
689           }
690         }
691 
692         pEnd++;
693       }
694 
695       wxString strKey(FilterInEntryName(wxString(pStart, pEnd).Trim()));
696 
697       // skip whitespace
698       while ( wxIsspace(*pEnd) )
699         pEnd++;
700 
701       if ( *pEnd++ != wxT('=') ) {
702         wxLogError(_("file '%s', line %d: '=' expected."),
703                    buffer.GetName(), n + 1);
704       }
705       else {
706         wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
707 
708         if ( pEntry == NULL ) {
709           // new entry
710           pEntry = m_pCurrentGroup->AddEntry(strKey, n);
711         }
712         else {
713           if ( bLocal && pEntry->IsImmutable() ) {
714             // immutable keys can't be changed by user
715             wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
716                          buffer.GetName(), n + 1, strKey.c_str());
717             continue;
718           }
719           // the condition below catches the cases (a) and (b) but not (c):
720           //  (a) global key found second time in global file
721           //  (b) key found second (or more) time in local file
722           //  (c) key from global file now found in local one
723           // which is exactly what we want.
724           else if ( !bLocal || pEntry->IsLocal() ) {
725             wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
726                          buffer.GetName(), n + 1, strKey.c_str(), pEntry->Line());
727 
728           }
729         }
730 
731         if ( bLocal )
732           pEntry->SetLine(m_linesTail);
733 
734         // skip whitespace
735         while ( wxIsspace(*pEnd) )
736           pEnd++;
737 
738         wxString value = pEnd;
739         if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS) )
740             value = FilterInValue(value);
741 
742         pEntry->SetValue(value, false);
743       }
744     }
745   }
746 }
747 
748 // ----------------------------------------------------------------------------
749 // set/retrieve path
750 // ----------------------------------------------------------------------------
751 
SetRootPath()752 void wxFileConfig::SetRootPath()
753 {
754     m_strPath.Empty();
755     m_pCurrentGroup = m_pRootGroup;
756 }
757 
758 bool
DoSetPath(const wxString & strPath,bool createMissingComponents)759 wxFileConfig::DoSetPath(const wxString& strPath, bool createMissingComponents)
760 {
761     wxArrayString aParts;
762 
763     if ( strPath.empty() ) {
764         SetRootPath();
765         return true;
766     }
767 
768     if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
769         // absolute path
770         wxSplitPath(aParts, strPath);
771     }
772     else {
773         // relative path, combine with current one
774         wxString strFullPath = m_strPath;
775         strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
776         wxSplitPath(aParts, strFullPath);
777     }
778 
779     // change current group
780     size_t n;
781     m_pCurrentGroup = m_pRootGroup;
782     for ( n = 0; n < aParts.Count(); n++ ) {
783         wxFileConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
784         if ( pNextGroup == NULL )
785         {
786             if ( !createMissingComponents )
787                 return false;
788 
789             pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
790         }
791 
792         m_pCurrentGroup = pNextGroup;
793     }
794 
795     // recombine path parts in one variable
796     m_strPath.Empty();
797     for ( n = 0; n < aParts.Count(); n++ ) {
798         m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
799     }
800 
801     return true;
802 }
803 
SetPath(const wxString & strPath)804 void wxFileConfig::SetPath(const wxString& strPath)
805 {
806     DoSetPath(strPath, true /* create missing path components */);
807 }
808 
809 // ----------------------------------------------------------------------------
810 // enumeration
811 // ----------------------------------------------------------------------------
812 
GetFirstGroup(wxString & str,long & lIndex) const813 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
814 {
815     lIndex = 0;
816     return GetNextGroup(str, lIndex);
817 }
818 
GetNextGroup(wxString & str,long & lIndex) const819 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
820 {
821     if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
822         str = m_pCurrentGroup->Groups()[(size_t)lIndex++]->Name();
823         return true;
824     }
825     else
826         return false;
827 }
828 
GetFirstEntry(wxString & str,long & lIndex) const829 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
830 {
831     lIndex = 0;
832     return GetNextEntry(str, lIndex);
833 }
834 
GetNextEntry(wxString & str,long & lIndex) const835 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
836 {
837     if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
838         str = m_pCurrentGroup->Entries()[(size_t)lIndex++]->Name();
839         return true;
840     }
841     else
842         return false;
843 }
844 
GetNumberOfEntries(bool bRecursive) const845 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
846 {
847     size_t n = m_pCurrentGroup->Entries().Count();
848     if ( bRecursive ) {
849         wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
850         size_t nSubgroups = m_pCurrentGroup->Groups().Count();
851         for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
852             CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
853             n += GetNumberOfEntries(true);
854             CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
855         }
856     }
857 
858     return n;
859 }
860 
GetNumberOfGroups(bool bRecursive) const861 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
862 {
863     size_t n = m_pCurrentGroup->Groups().Count();
864     if ( bRecursive ) {
865         wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
866         size_t nSubgroups = m_pCurrentGroup->Groups().Count();
867         for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
868             CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
869             n += GetNumberOfGroups(true);
870             CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
871         }
872     }
873 
874     return n;
875 }
876 
877 // ----------------------------------------------------------------------------
878 // tests for existence
879 // ----------------------------------------------------------------------------
880 
HasGroup(const wxString & strName) const881 bool wxFileConfig::HasGroup(const wxString& strName) const
882 {
883     // special case: DoSetPath("") does work as it's equivalent to DoSetPath("/")
884     // but there is no group with empty name so treat this separately
885     if ( strName.empty() )
886         return false;
887 
888     const wxString pathOld = GetPath();
889 
890     wxFileConfig *self = wx_const_cast(wxFileConfig *, this);
891     const bool
892         rc = self->DoSetPath(strName, false /* don't create missing components */);
893 
894     self->SetPath(pathOld);
895 
896     return rc;
897 }
898 
HasEntry(const wxString & entry) const899 bool wxFileConfig::HasEntry(const wxString& entry) const
900 {
901     // path is the part before the last "/"
902     wxString path = entry.BeforeLast(wxCONFIG_PATH_SEPARATOR);
903 
904     // except in the special case of "/keyname" when there is nothing before "/"
905     if ( path.empty() && *entry.c_str() == wxCONFIG_PATH_SEPARATOR )
906     {
907         path = wxCONFIG_PATH_SEPARATOR;
908     }
909 
910     // change to the path of the entry if necessary and remember the old path
911     // to restore it later
912     wxString pathOld;
913     wxFileConfig * const self = wx_const_cast(wxFileConfig *, this);
914     if ( !path.empty() )
915     {
916         pathOld = GetPath();
917         if ( pathOld.empty() )
918             pathOld = wxCONFIG_PATH_SEPARATOR;
919 
920         if ( !self->DoSetPath(path, false /* don't create if doesn't exist */) )
921         {
922             return false;
923         }
924     }
925 
926     // check if the entry exists in this group
927     const bool exists = m_pCurrentGroup->FindEntry(
928                             entry.AfterLast(wxCONFIG_PATH_SEPARATOR)) != NULL;
929 
930     // restore the old path if we changed it above
931     if ( !pathOld.empty() )
932     {
933         self->SetPath(pathOld);
934     }
935 
936     return exists;
937 }
938 
939 // ----------------------------------------------------------------------------
940 // read/write values
941 // ----------------------------------------------------------------------------
942 
DoReadString(const wxString & key,wxString * pStr) const943 bool wxFileConfig::DoReadString(const wxString& key, wxString* pStr) const
944 {
945     wxConfigPathChanger path(this, key);
946 
947     wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
948     if (pEntry == NULL) {
949         return false;
950     }
951 
952     *pStr = pEntry->Value();
953 
954     return true;
955 }
956 
DoReadLong(const wxString & key,long * pl) const957 bool wxFileConfig::DoReadLong(const wxString& key, long *pl) const
958 {
959     wxString str;
960     if ( !Read(key, &str) )
961         return false;
962 
963     // extra spaces shouldn't prevent us from reading numeric values
964     str.Trim();
965 
966     return str.ToLong(pl);
967 }
968 
DoWriteString(const wxString & key,const wxString & szValue)969 bool wxFileConfig::DoWriteString(const wxString& key, const wxString& szValue)
970 {
971     wxConfigPathChanger     path(this, key);
972     wxString                strName = path.Name();
973 
974     wxLogTrace( FILECONF_TRACE_MASK,
975                 _T("  Writing String '%s' = '%s' to Group '%s'"),
976                 strName.c_str(),
977                 szValue.c_str(),
978                 GetPath().c_str() );
979 
980     if ( strName.empty() )
981     {
982             // setting the value of a group is an error
983 
984         wxASSERT_MSG( szValue.empty(), wxT("can't set value of a group!") );
985 
986             // ... except if it's empty in which case it's a way to force it's creation
987 
988         wxLogTrace( FILECONF_TRACE_MASK,
989                     _T("  Creating group %s"),
990                     m_pCurrentGroup->Name().c_str() );
991 
992         SetDirty();
993 
994         // this will add a line for this group if it didn't have it before (or
995         // do nothing for the root but it's ok as it always exists anyhow)
996         (void)m_pCurrentGroup->GetGroupLine();
997     }
998     else
999     {
1000         // writing an entry check that the name is reasonable
1001         if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX )
1002         {
1003             wxLogError( _("Config entry name cannot start with '%c'."),
1004                         wxCONFIG_IMMUTABLE_PREFIX);
1005             return false;
1006         }
1007 
1008         wxFileConfigEntry   *pEntry = m_pCurrentGroup->FindEntry(strName);
1009 
1010         if ( pEntry == 0 )
1011         {
1012             wxLogTrace( FILECONF_TRACE_MASK,
1013                         _T("  Adding Entry %s"),
1014                         strName.c_str() );
1015             pEntry = m_pCurrentGroup->AddEntry(strName);
1016         }
1017 
1018         wxLogTrace( FILECONF_TRACE_MASK,
1019                     _T("  Setting value %s"),
1020                     szValue.c_str() );
1021         pEntry->SetValue(szValue);
1022 
1023         SetDirty();
1024     }
1025 
1026     return true;
1027 }
1028 
DoWriteLong(const wxString & key,long lValue)1029 bool wxFileConfig::DoWriteLong(const wxString& key, long lValue)
1030 {
1031   return Write(key, wxString::Format(_T("%ld"), lValue));
1032 }
1033 
Flush(bool)1034 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
1035 {
1036   if ( !IsDirty() || !m_strLocalFile )
1037     return true;
1038 
1039   // set the umask if needed
1040   wxCHANGE_UMASK(m_umask);
1041 
1042   wxTempFile file(m_strLocalFile);
1043 
1044   if ( !file.IsOpened() )
1045   {
1046     wxLogError(_("can't open user configuration file."));
1047     return false;
1048   }
1049 
1050   // write all strings to file
1051   wxString filetext;
1052   filetext.reserve(4096);
1053   for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
1054   {
1055     filetext << p->Text() << wxTextFile::GetEOL();
1056   }
1057 
1058   if ( !file.Write(filetext, *m_conv) )
1059   {
1060     wxLogError(_("can't write user configuration file."));
1061     return false;
1062   }
1063 
1064   if ( !file.Commit() )
1065   {
1066       wxLogError(_("Failed to update user configuration file."));
1067 
1068       return false;
1069   }
1070 
1071   ResetDirty();
1072 
1073 #if defined(__WXMAC__)
1074   wxFileName(m_strLocalFile).MacSetTypeAndCreator('TEXT', 'ttxt');
1075 #endif // __WXMAC__
1076 
1077   return true;
1078 }
1079 
1080 #if wxUSE_STREAMS
1081 
Save(wxOutputStream & os,const wxMBConv & conv)1082 bool wxFileConfig::Save(wxOutputStream& os, const wxMBConv& conv)
1083 {
1084     // save unconditionally, even if not dirty
1085     for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
1086     {
1087         wxString line = p->Text();
1088         line += wxTextFile::GetEOL();
1089 
1090         wxCharBuffer buf(line.mb_str(conv));
1091         if ( !os.Write(buf, strlen(buf)) )
1092         {
1093             wxLogError(_("Error saving user configuration data."));
1094 
1095             return false;
1096         }
1097     }
1098 
1099     ResetDirty();
1100 
1101     return true;
1102 }
1103 
1104 #endif // wxUSE_STREAMS
1105 
1106 // ----------------------------------------------------------------------------
1107 // renaming groups/entries
1108 // ----------------------------------------------------------------------------
1109 
RenameEntry(const wxString & oldName,const wxString & newName)1110 bool wxFileConfig::RenameEntry(const wxString& oldName,
1111                                const wxString& newName)
1112 {
1113     wxASSERT_MSG( !wxStrchr(oldName, wxCONFIG_PATH_SEPARATOR),
1114                    _T("RenameEntry(): paths are not supported") );
1115 
1116     // check that the entry exists
1117     wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
1118     if ( !oldEntry )
1119         return false;
1120 
1121     // check that the new entry doesn't already exist
1122     if ( m_pCurrentGroup->FindEntry(newName) )
1123         return false;
1124 
1125     // delete the old entry, create the new one
1126     wxString value = oldEntry->Value();
1127     if ( !m_pCurrentGroup->DeleteEntry(oldName) )
1128         return false;
1129 
1130     SetDirty();
1131 
1132     wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
1133     newEntry->SetValue(value);
1134 
1135     return true;
1136 }
1137 
RenameGroup(const wxString & oldName,const wxString & newName)1138 bool wxFileConfig::RenameGroup(const wxString& oldName,
1139                                const wxString& newName)
1140 {
1141     // check that the group exists
1142     wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
1143     if ( !group )
1144         return false;
1145 
1146     // check that the new group doesn't already exist
1147     if ( m_pCurrentGroup->FindSubgroup(newName) )
1148         return false;
1149 
1150     group->Rename(newName);
1151 
1152     SetDirty();
1153 
1154     return true;
1155 }
1156 
1157 // ----------------------------------------------------------------------------
1158 // delete groups/entries
1159 // ----------------------------------------------------------------------------
1160 
DeleteEntry(const wxString & key,bool bGroupIfEmptyAlso)1161 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
1162 {
1163   wxConfigPathChanger path(this, key);
1164 
1165   if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
1166     return false;
1167 
1168   SetDirty();
1169 
1170   if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
1171     if ( m_pCurrentGroup != m_pRootGroup ) {
1172       wxFileConfigGroup *pGroup = m_pCurrentGroup;
1173       SetPath(wxT(".."));  // changes m_pCurrentGroup!
1174       m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
1175     }
1176     //else: never delete the root group
1177   }
1178 
1179   return true;
1180 }
1181 
DeleteGroup(const wxString & key)1182 bool wxFileConfig::DeleteGroup(const wxString& key)
1183 {
1184   wxConfigPathChanger path(this, RemoveTrailingSeparator(key));
1185 
1186   if ( !m_pCurrentGroup->DeleteSubgroupByName(path.Name()) )
1187       return false;
1188 
1189   path.UpdateIfDeleted();
1190 
1191   SetDirty();
1192 
1193   return true;
1194 }
1195 
DeleteAll()1196 bool wxFileConfig::DeleteAll()
1197 {
1198   CleanUp();
1199 
1200   if ( !m_strLocalFile.empty() )
1201   {
1202       if ( wxFile::Exists(m_strLocalFile) && wxRemove(m_strLocalFile) == -1 )
1203       {
1204           wxLogSysError(_("can't delete user configuration file '%s'"),
1205                         m_strLocalFile.c_str());
1206           return false;
1207       }
1208   }
1209 
1210   Init();
1211 
1212   return true;
1213 }
1214 
1215 // ----------------------------------------------------------------------------
1216 // linked list functions
1217 // ----------------------------------------------------------------------------
1218 
1219     // append a new line to the end of the list
1220 
LineListAppend(const wxString & str)1221 wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
1222 {
1223     wxLogTrace( FILECONF_TRACE_MASK,
1224                 _T("    ** Adding Line '%s'"),
1225                 str.c_str() );
1226     wxLogTrace( FILECONF_TRACE_MASK,
1227                 _T("        head: %s"),
1228                 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1229     wxLogTrace( FILECONF_TRACE_MASK,
1230                 _T("        tail: %s"),
1231                 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1232 
1233     wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
1234 
1235     if ( m_linesTail == NULL )
1236     {
1237         // list is empty
1238         m_linesHead = pLine;
1239     }
1240     else
1241     {
1242         // adjust pointers
1243         m_linesTail->SetNext(pLine);
1244         pLine->SetPrev(m_linesTail);
1245     }
1246 
1247     m_linesTail = pLine;
1248 
1249     wxLogTrace( FILECONF_TRACE_MASK,
1250                 _T("        head: %s"),
1251                 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1252     wxLogTrace( FILECONF_TRACE_MASK,
1253                 _T("        tail: %s"),
1254                 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1255 
1256     return m_linesTail;
1257 }
1258 
1259 // insert a new line after the given one or in the very beginning if !pLine
LineListInsert(const wxString & str,wxFileConfigLineList * pLine)1260 wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
1261                                                    wxFileConfigLineList *pLine)
1262 {
1263     wxLogTrace( FILECONF_TRACE_MASK,
1264                 _T("    ** Inserting Line '%s' after '%s'"),
1265                 str.c_str(),
1266                 ((pLine) ? pLine->Text().c_str() : wxEmptyString) );
1267     wxLogTrace( FILECONF_TRACE_MASK,
1268                 _T("        head: %s"),
1269                 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1270     wxLogTrace( FILECONF_TRACE_MASK,
1271                 _T("        tail: %s"),
1272                 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1273 
1274     if ( pLine == m_linesTail )
1275         return LineListAppend(str);
1276 
1277     wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1278     if ( pLine == NULL )
1279     {
1280         // prepend to the list
1281         pNewLine->SetNext(m_linesHead);
1282         m_linesHead->SetPrev(pNewLine);
1283         m_linesHead = pNewLine;
1284     }
1285     else
1286     {
1287         // insert before pLine
1288         wxFileConfigLineList *pNext = pLine->Next();
1289         pNewLine->SetNext(pNext);
1290         pNewLine->SetPrev(pLine);
1291         pNext->SetPrev(pNewLine);
1292         pLine->SetNext(pNewLine);
1293     }
1294 
1295     wxLogTrace( FILECONF_TRACE_MASK,
1296                 _T("        head: %s"),
1297                 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1298     wxLogTrace( FILECONF_TRACE_MASK,
1299                 _T("        tail: %s"),
1300                 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1301 
1302     return pNewLine;
1303 }
1304 
LineListRemove(wxFileConfigLineList * pLine)1305 void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
1306 {
1307     wxLogTrace( FILECONF_TRACE_MASK,
1308                 _T("    ** Removing Line '%s'"),
1309                 pLine->Text().c_str() );
1310     wxLogTrace( FILECONF_TRACE_MASK,
1311                 _T("        head: %s"),
1312                 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1313     wxLogTrace( FILECONF_TRACE_MASK,
1314                 _T("        tail: %s"),
1315                 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1316 
1317     wxFileConfigLineList    *pPrev = pLine->Prev(),
1318                             *pNext = pLine->Next();
1319 
1320         // first entry?
1321 
1322     if ( pPrev == NULL )
1323         m_linesHead = pNext;
1324     else
1325         pPrev->SetNext(pNext);
1326 
1327         // last entry?
1328 
1329     if ( pNext == NULL )
1330         m_linesTail = pPrev;
1331     else
1332         pNext->SetPrev(pPrev);
1333 
1334     if ( m_pRootGroup->GetGroupLine() == pLine )
1335         m_pRootGroup->SetLine(m_linesHead);
1336 
1337     wxLogTrace( FILECONF_TRACE_MASK,
1338                 _T("        head: %s"),
1339                 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1340     wxLogTrace( FILECONF_TRACE_MASK,
1341                 _T("        tail: %s"),
1342                 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1343 
1344     delete pLine;
1345 }
1346 
LineListIsEmpty()1347 bool wxFileConfig::LineListIsEmpty()
1348 {
1349     return m_linesHead == NULL;
1350 }
1351 
1352 // ============================================================================
1353 // wxFileConfig::wxFileConfigGroup
1354 // ============================================================================
1355 
1356 // ----------------------------------------------------------------------------
1357 // ctor/dtor
1358 // ----------------------------------------------------------------------------
1359 
1360 // ctor
wxFileConfigGroup(wxFileConfigGroup * pParent,const wxString & strName,wxFileConfig * pConfig)1361 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
1362                                        const wxString& strName,
1363                                        wxFileConfig *pConfig)
1364                          : m_aEntries(CompareEntries),
1365                            m_aSubgroups(CompareGroups),
1366                            m_strName(strName)
1367 {
1368   m_pConfig = pConfig;
1369   m_pParent = pParent;
1370   m_pLine   = NULL;
1371 
1372   m_pLastEntry = NULL;
1373   m_pLastGroup = NULL;
1374 }
1375 
1376 // dtor deletes all children
~wxFileConfigGroup()1377 wxFileConfigGroup::~wxFileConfigGroup()
1378 {
1379   // entries
1380   size_t n, nCount = m_aEntries.Count();
1381   for ( n = 0; n < nCount; n++ )
1382     delete m_aEntries[n];
1383 
1384   // subgroups
1385   nCount = m_aSubgroups.Count();
1386   for ( n = 0; n < nCount; n++ )
1387     delete m_aSubgroups[n];
1388 }
1389 
1390 // ----------------------------------------------------------------------------
1391 // line
1392 // ----------------------------------------------------------------------------
1393 
SetLine(wxFileConfigLineList * pLine)1394 void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
1395 {
1396     // for a normal (i.e. not root) group this method shouldn't be called twice
1397     // unless we are resetting the line
1398     wxASSERT_MSG( !m_pParent || !m_pLine || !pLine,
1399                    _T("changing line for a non-root group?") );
1400 
1401     m_pLine = pLine;
1402 }
1403 
1404 /*
1405   This is a bit complicated, so let me explain it in details. All lines that
1406   were read from the local file (the only one we will ever modify) are stored
1407   in a (doubly) linked list. Our problem is to know at which position in this
1408   list should we insert the new entries/subgroups. To solve it we keep three
1409   variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1410 
1411   m_pLine points to the line containing "[group_name]"
1412   m_pLastEntry points to the last entry of this group in the local file.
1413   m_pLastGroup                   subgroup
1414 
1415   Initially, they're NULL all three. When the group (an entry/subgroup) is read
1416   from the local file, the corresponding variable is set. However, if the group
1417   was read from the global file and then modified or created by the application
1418   these variables are still NULL and we need to create the corresponding lines.
1419   See the following functions (and comments preceding them) for the details of
1420   how we do it.
1421 
1422   Also, when our last entry/group are deleted we need to find the new last
1423   element - the code in DeleteEntry/Subgroup does this by backtracking the list
1424   of lines until it either founds an entry/subgroup (and this is the new last
1425   element) or the m_pLine of the group, in which case there are no more entries
1426   (or subgroups) left and m_pLast<element> becomes NULL.
1427 
1428   NB: This last problem could be avoided for entries if we added new entries
1429       immediately after m_pLine, but in this case the entries would appear
1430       backwards in the config file (OTOH, it's not that important) and as we
1431       would still need to do it for the subgroups the code wouldn't have been
1432       significantly less complicated.
1433 */
1434 
1435 // Return the line which contains "[our name]". If we're still not in the list,
1436 // add our line to it immediately after the last line of our parent group if we
1437 // have it or in the very beginning if we're the root group.
GetGroupLine()1438 wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
1439 {
1440     wxLogTrace( FILECONF_TRACE_MASK,
1441                 _T("  GetGroupLine() for Group '%s'"),
1442                 Name().c_str() );
1443 
1444     if ( !m_pLine )
1445     {
1446         wxLogTrace( FILECONF_TRACE_MASK,
1447                     _T("    Getting Line item pointer") );
1448 
1449         wxFileConfigGroup   *pParent = Parent();
1450 
1451         // this group wasn't present in local config file, add it now
1452         if ( pParent )
1453         {
1454             wxLogTrace( FILECONF_TRACE_MASK,
1455                         _T("    checking parent '%s'"),
1456                         pParent->Name().c_str() );
1457 
1458             wxString    strFullName;
1459 
1460             // add 1 to the name because we don't want to start with '/'
1461             strFullName << wxT("[")
1462                         << FilterOutEntryName(GetFullName().c_str() + 1)
1463                         << wxT("]");
1464             m_pLine = m_pConfig->LineListInsert(strFullName,
1465                                                 pParent->GetLastGroupLine());
1466             pParent->SetLastGroup(this);  // we're surely after all the others
1467         }
1468         //else: this is the root group and so we return NULL because we don't
1469         //      have any group line
1470     }
1471 
1472     return m_pLine;
1473 }
1474 
1475 // Return the last line belonging to the subgroups of this group (after which
1476 // we can add a new subgroup), if we don't have any subgroups or entries our
1477 // last line is the group line (m_pLine) itself.
GetLastGroupLine()1478 wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
1479 {
1480     // if we have any subgroups, our last line is the last line of the last
1481     // subgroup
1482     if ( m_pLastGroup )
1483     {
1484         wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1485 
1486         wxASSERT_MSG( pLine, _T("last group must have !NULL associated line") );
1487 
1488         return pLine;
1489     }
1490 
1491     // no subgroups, so the last line is the line of thelast entry (if any)
1492     return GetLastEntryLine();
1493 }
1494 
1495 // return the last line belonging to the entries of this group (after which
1496 // we can add a new entry), if we don't have any entries we will add the new
1497 // one immediately after the group line itself.
GetLastEntryLine()1498 wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
1499 {
1500     wxLogTrace( FILECONF_TRACE_MASK,
1501                 _T("  GetLastEntryLine() for Group '%s'"),
1502                 Name().c_str() );
1503 
1504     if ( m_pLastEntry )
1505     {
1506         wxFileConfigLineList    *pLine = m_pLastEntry->GetLine();
1507 
1508         wxASSERT_MSG( pLine, _T("last entry must have !NULL associated line") );
1509 
1510         return pLine;
1511     }
1512 
1513     // no entries: insert after the group header, if any
1514     return GetGroupLine();
1515 }
1516 
SetLastEntry(wxFileConfigEntry * pEntry)1517 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry *pEntry)
1518 {
1519     m_pLastEntry = pEntry;
1520 
1521     if ( !m_pLine )
1522     {
1523         // the only situation in which a group without its own line can have
1524         // an entry is when the first entry is added to the initially empty
1525         // root pseudo-group
1526         wxASSERT_MSG( !m_pParent, _T("unexpected for non root group") );
1527 
1528         // let the group know that it does have a line in the file now
1529         m_pLine = pEntry->GetLine();
1530     }
1531 }
1532 
1533 // ----------------------------------------------------------------------------
1534 // group name
1535 // ----------------------------------------------------------------------------
1536 
UpdateGroupAndSubgroupsLines()1537 void wxFileConfigGroup::UpdateGroupAndSubgroupsLines()
1538 {
1539     // update the line of this group
1540     wxFileConfigLineList *line = GetGroupLine();
1541     wxCHECK_RET( line, _T("a non root group must have a corresponding line!") );
1542 
1543     // +1: skip the leading '/'
1544     line->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1));
1545 
1546 
1547     // also update all subgroups as they have this groups name in their lines
1548     const size_t nCount = m_aSubgroups.Count();
1549     for ( size_t n = 0; n < nCount; n++ )
1550     {
1551         m_aSubgroups[n]->UpdateGroupAndSubgroupsLines();
1552     }
1553 }
1554 
Rename(const wxString & newName)1555 void wxFileConfigGroup::Rename(const wxString& newName)
1556 {
1557     wxCHECK_RET( m_pParent, _T("the root group can't be renamed") );
1558 
1559     if ( newName == m_strName )
1560         return;
1561 
1562     // we need to remove the group from the parent and it back under the new
1563     // name to keep the parents array of subgroups alphabetically sorted
1564     m_pParent->m_aSubgroups.Remove(this);
1565 
1566     m_strName = newName;
1567 
1568     m_pParent->m_aSubgroups.Add(this);
1569 
1570     // update the group lines recursively
1571     UpdateGroupAndSubgroupsLines();
1572 }
1573 
GetFullName() const1574 wxString wxFileConfigGroup::GetFullName() const
1575 {
1576     wxString fullname;
1577     if ( Parent() )
1578         fullname = Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1579 
1580     return fullname;
1581 }
1582 
1583 // ----------------------------------------------------------------------------
1584 // find an item
1585 // ----------------------------------------------------------------------------
1586 
1587 // use binary search because the array is sorted
1588 wxFileConfigEntry *
FindEntry(const wxChar * szName) const1589 wxFileConfigGroup::FindEntry(const wxChar *szName) const
1590 {
1591   size_t i,
1592        lo = 0,
1593        hi = m_aEntries.Count();
1594   int res;
1595   wxFileConfigEntry *pEntry;
1596 
1597   while ( lo < hi ) {
1598     i = (lo + hi)/2;
1599     pEntry = m_aEntries[i];
1600 
1601     #if wxCONFIG_CASE_SENSITIVE
1602       res = wxStrcmp(pEntry->Name(), szName);
1603     #else
1604       res = wxStricmp(pEntry->Name(), szName);
1605     #endif
1606 
1607     if ( res > 0 )
1608       hi = i;
1609     else if ( res < 0 )
1610       lo = i + 1;
1611     else
1612       return pEntry;
1613   }
1614 
1615   return NULL;
1616 }
1617 
1618 wxFileConfigGroup *
FindSubgroup(const wxChar * szName) const1619 wxFileConfigGroup::FindSubgroup(const wxChar *szName) const
1620 {
1621   size_t i,
1622        lo = 0,
1623        hi = m_aSubgroups.Count();
1624   int res;
1625   wxFileConfigGroup *pGroup;
1626 
1627   while ( lo < hi ) {
1628     i = (lo + hi)/2;
1629     pGroup = m_aSubgroups[i];
1630 
1631     #if wxCONFIG_CASE_SENSITIVE
1632       res = wxStrcmp(pGroup->Name(), szName);
1633     #else
1634       res = wxStricmp(pGroup->Name(), szName);
1635     #endif
1636 
1637     if ( res > 0 )
1638       hi = i;
1639     else if ( res < 0 )
1640       lo = i + 1;
1641     else
1642       return pGroup;
1643   }
1644 
1645   return NULL;
1646 }
1647 
1648 // ----------------------------------------------------------------------------
1649 // create a new item
1650 // ----------------------------------------------------------------------------
1651 
1652 // create a new entry and add it to the current group
AddEntry(const wxString & strName,int nLine)1653 wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
1654 {
1655     wxASSERT( FindEntry(strName) == 0 );
1656 
1657     wxFileConfigEntry   *pEntry = new wxFileConfigEntry(this, strName, nLine);
1658 
1659     m_aEntries.Add(pEntry);
1660     return pEntry;
1661 }
1662 
1663 // create a new group and add it to the current group
AddSubgroup(const wxString & strName)1664 wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName)
1665 {
1666     wxASSERT( FindSubgroup(strName) == 0 );
1667 
1668     wxFileConfigGroup   *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
1669 
1670     m_aSubgroups.Add(pGroup);
1671     return pGroup;
1672 }
1673 
1674 // ----------------------------------------------------------------------------
1675 // delete an item
1676 // ----------------------------------------------------------------------------
1677 
1678 /*
1679   The delete operations are _very_ slow if we delete the last item of this
1680   group (see comments before GetXXXLineXXX functions for more details),
1681   so it's much better to start with the first entry/group if we want to
1682   delete several of them.
1683  */
1684 
DeleteSubgroupByName(const wxChar * szName)1685 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1686 {
1687     wxFileConfigGroup * const pGroup = FindSubgroup(szName);
1688 
1689     return pGroup ? DeleteSubgroup(pGroup) : false;
1690 }
1691 
1692 // Delete the subgroup and remove all references to it from
1693 // other data structures.
DeleteSubgroup(wxFileConfigGroup * pGroup)1694 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
1695 {
1696     wxCHECK_MSG( pGroup, false, _T("deleting non existing group?") );
1697 
1698     wxLogTrace( FILECONF_TRACE_MASK,
1699                 _T("Deleting group '%s' from '%s'"),
1700                 pGroup->Name().c_str(),
1701                 Name().c_str() );
1702 
1703     wxLogTrace( FILECONF_TRACE_MASK,
1704                 _T("  (m_pLine) = prev: %p, this %p, next %p"),
1705                 m_pLine ? wx_static_cast(void*, m_pLine->Prev()) : 0,
1706                 wx_static_cast(void*, m_pLine),
1707                 m_pLine ? wx_static_cast(void*, m_pLine->Next()) : 0 );
1708     wxLogTrace( FILECONF_TRACE_MASK,
1709                 _T("  text: '%s'"),
1710                 m_pLine ? m_pLine->Text().c_str() : wxEmptyString );
1711 
1712     // delete all entries...
1713     size_t nCount = pGroup->m_aEntries.Count();
1714 
1715     wxLogTrace(FILECONF_TRACE_MASK,
1716                _T("Removing %lu entries"), (unsigned long)nCount );
1717 
1718     for ( size_t nEntry = 0; nEntry < nCount; nEntry++ )
1719     {
1720         wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1721 
1722         if ( pLine )
1723         {
1724             wxLogTrace( FILECONF_TRACE_MASK,
1725                         _T("    '%s'"),
1726                         pLine->Text().c_str() );
1727             m_pConfig->LineListRemove(pLine);
1728         }
1729     }
1730 
1731     // ...and subgroups of this subgroup
1732     nCount = pGroup->m_aSubgroups.Count();
1733 
1734     wxLogTrace( FILECONF_TRACE_MASK,
1735                 _T("Removing %lu subgroups"), (unsigned long)nCount );
1736 
1737     for ( size_t nGroup = 0; nGroup < nCount; nGroup++ )
1738     {
1739         pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1740     }
1741 
1742     // and then finally the group itself
1743     wxFileConfigLineList *pLine = pGroup->m_pLine;
1744     if ( pLine )
1745     {
1746         wxLogTrace( FILECONF_TRACE_MASK,
1747                     _T("  Removing line for group '%s' : '%s'"),
1748                     pGroup->Name().c_str(),
1749                     pLine->Text().c_str() );
1750         wxLogTrace( FILECONF_TRACE_MASK,
1751                     _T("  Removing from group '%s' : '%s'"),
1752                     Name().c_str(),
1753                     ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1754 
1755         // notice that we may do this test inside the previous "if"
1756         // because the last entry's line is surely !NULL
1757         if ( pGroup == m_pLastGroup )
1758         {
1759             wxLogTrace( FILECONF_TRACE_MASK,
1760                         _T("  Removing last group") );
1761 
1762             // our last entry is being deleted, so find the last one which
1763             // stays by going back until we find a subgroup or reach the
1764             // group line
1765             const size_t nSubgroups = m_aSubgroups.Count();
1766 
1767             m_pLastGroup = NULL;
1768             for ( wxFileConfigLineList *pl = pLine->Prev();
1769                   pl && !m_pLastGroup;
1770                   pl = pl->Prev() )
1771             {
1772                 // does this line belong to our subgroup?
1773                 for ( size_t n = 0; n < nSubgroups; n++ )
1774                 {
1775                     // do _not_ call GetGroupLine! we don't want to add it to
1776                     // the local file if it's not already there
1777                     if ( m_aSubgroups[n]->m_pLine == pl )
1778                     {
1779                         m_pLastGroup = m_aSubgroups[n];
1780                         break;
1781                     }
1782                 }
1783 
1784                 if ( pl == m_pLine )
1785                     break;
1786             }
1787         }
1788 
1789         m_pConfig->LineListRemove(pLine);
1790     }
1791     else
1792     {
1793         wxLogTrace( FILECONF_TRACE_MASK,
1794                     _T("  No line entry for Group '%s'?"),
1795                     pGroup->Name().c_str() );
1796     }
1797 
1798     m_aSubgroups.Remove(pGroup);
1799     delete pGroup;
1800 
1801     return true;
1802 }
1803 
DeleteEntry(const wxChar * szName)1804 bool wxFileConfigGroup::DeleteEntry(const wxChar *szName)
1805 {
1806   wxFileConfigEntry *pEntry = FindEntry(szName);
1807   if ( !pEntry )
1808   {
1809       // entry doesn't exist, nothing to do
1810       return false;
1811   }
1812 
1813   wxFileConfigLineList *pLine = pEntry->GetLine();
1814   if ( pLine != NULL ) {
1815     // notice that we may do this test inside the previous "if" because the
1816     // last entry's line is surely !NULL
1817     if ( pEntry == m_pLastEntry ) {
1818       // our last entry is being deleted - find the last one which stays
1819       wxASSERT( m_pLine != NULL );  // if we have an entry with !NULL pLine...
1820 
1821       // find the previous entry (if any)
1822       wxFileConfigEntry *pNewLast = NULL;
1823       const wxFileConfigLineList * const
1824         pNewLastLine = m_pLastEntry->GetLine()->Prev();
1825       const size_t nEntries = m_aEntries.GetCount();
1826       for ( size_t n = 0; n < nEntries; n++ ) {
1827         if ( m_aEntries[n]->GetLine() == pNewLastLine ) {
1828           pNewLast = m_aEntries[n];
1829           break;
1830         }
1831       }
1832 
1833       // pNewLast can be NULL here -- it's ok and can happen if we have no
1834       // entries left
1835       m_pLastEntry = pNewLast;
1836     }
1837 
1838     m_pConfig->LineListRemove(pLine);
1839   }
1840 
1841   m_aEntries.Remove(pEntry);
1842   delete pEntry;
1843 
1844   return true;
1845 }
1846 
1847 // ============================================================================
1848 // wxFileConfig::wxFileConfigEntry
1849 // ============================================================================
1850 
1851 // ----------------------------------------------------------------------------
1852 // ctor
1853 // ----------------------------------------------------------------------------
wxFileConfigEntry(wxFileConfigGroup * pParent,const wxString & strName,int nLine)1854 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent,
1855                                        const wxString& strName,
1856                                        int nLine)
1857                          : m_strName(strName)
1858 {
1859   wxASSERT( !strName.empty() );
1860 
1861   m_pParent = pParent;
1862   m_nLine   = nLine;
1863   m_pLine   = NULL;
1864 
1865   m_bHasValue = false;
1866 
1867   m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1868   if ( m_bImmutable )
1869     m_strName.erase(0, 1);  // remove first character
1870 }
1871 
1872 // ----------------------------------------------------------------------------
1873 // set value
1874 // ----------------------------------------------------------------------------
1875 
SetLine(wxFileConfigLineList * pLine)1876 void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine)
1877 {
1878   if ( m_pLine != NULL ) {
1879     wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1880                  Name().c_str(), m_pParent->GetFullName().c_str());
1881   }
1882 
1883   m_pLine = pLine;
1884   Group()->SetLastEntry(this);
1885 }
1886 
1887 // second parameter is false if we read the value from file and prevents the
1888 // entry from being marked as 'dirty'
SetValue(const wxString & strValue,bool bUser)1889 void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser)
1890 {
1891     if ( bUser && IsImmutable() )
1892     {
1893         wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1894                       Name().c_str());
1895         return;
1896     }
1897 
1898     // do nothing if it's the same value: but don't test for it if m_bHasValue
1899     // hadn't been set yet or we'd never write empty values to the file
1900     if ( m_bHasValue && strValue == m_strValue )
1901         return;
1902 
1903     m_bHasValue = true;
1904     m_strValue = strValue;
1905 
1906     if ( bUser )
1907     {
1908         wxString strValFiltered;
1909 
1910         if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS )
1911         {
1912             strValFiltered = strValue;
1913         }
1914         else {
1915             strValFiltered = FilterOutValue(strValue);
1916         }
1917 
1918         wxString    strLine;
1919         strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered;
1920 
1921         if ( m_pLine )
1922         {
1923             // entry was read from the local config file, just modify the line
1924             m_pLine->SetText(strLine);
1925         }
1926         else // this entry didn't exist in the local file
1927         {
1928             // add a new line to the file: note that line returned by
1929             // GetLastEntryLine() may be NULL if we're in the root group and it
1930             // doesn't have any entries yet, but this is ok as passing NULL
1931             // line to LineListInsert() means to prepend new line to the list
1932             wxFileConfigLineList *line = Group()->GetLastEntryLine();
1933             m_pLine = Group()->Config()->LineListInsert(strLine, line);
1934 
1935             Group()->SetLastEntry(this);
1936         }
1937     }
1938 }
1939 
1940 // ============================================================================
1941 // global functions
1942 // ============================================================================
1943 
1944 // ----------------------------------------------------------------------------
1945 // compare functions for array sorting
1946 // ----------------------------------------------------------------------------
1947 
CompareEntries(wxFileConfigEntry * p1,wxFileConfigEntry * p2)1948 int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
1949 {
1950 #if wxCONFIG_CASE_SENSITIVE
1951     return wxStrcmp(p1->Name(), p2->Name());
1952 #else
1953     return wxStricmp(p1->Name(), p2->Name());
1954 #endif
1955 }
1956 
CompareGroups(wxFileConfigGroup * p1,wxFileConfigGroup * p2)1957 int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
1958 {
1959 #if wxCONFIG_CASE_SENSITIVE
1960     return wxStrcmp(p1->Name(), p2->Name());
1961 #else
1962     return wxStricmp(p1->Name(), p2->Name());
1963 #endif
1964 }
1965 
1966 // ----------------------------------------------------------------------------
1967 // filter functions
1968 // ----------------------------------------------------------------------------
1969 
1970 // undo FilterOutValue
FilterInValue(const wxString & str)1971 static wxString FilterInValue(const wxString& str)
1972 {
1973   wxString strResult;
1974   strResult.Alloc(str.Len());
1975 
1976   bool bQuoted = !str.empty() && str[0] == '"';
1977 
1978   for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1979     if ( str[n] == wxT('\\') ) {
1980       switch ( str[++n] ) {
1981         case wxT('n'):
1982           strResult += wxT('\n');
1983           break;
1984 
1985         case wxT('r'):
1986           strResult += wxT('\r');
1987           break;
1988 
1989         case wxT('t'):
1990           strResult += wxT('\t');
1991           break;
1992 
1993         case wxT('\\'):
1994           strResult += wxT('\\');
1995           break;
1996 
1997         case wxT('"'):
1998           strResult += wxT('"');
1999           break;
2000       }
2001     }
2002     else {
2003       if ( str[n] != wxT('"') || !bQuoted )
2004         strResult += str[n];
2005       else if ( n != str.Len() - 1 ) {
2006         wxLogWarning(_("unexpected \" at position %d in '%s'."),
2007                      n, str.c_str());
2008       }
2009       //else: it's the last quote of a quoted string, ok
2010     }
2011   }
2012 
2013   return strResult;
2014 }
2015 
2016 // quote the string before writing it to file
FilterOutValue(const wxString & str)2017 static wxString FilterOutValue(const wxString& str)
2018 {
2019    if ( !str )
2020       return str;
2021 
2022   wxString strResult;
2023   strResult.Alloc(str.Len());
2024 
2025   // quoting is necessary to preserve spaces in the beginning of the string
2026   bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
2027 
2028   if ( bQuote )
2029     strResult += wxT('"');
2030 
2031   wxChar c;
2032   for ( size_t n = 0; n < str.Len(); n++ ) {
2033     switch ( str[n] ) {
2034       case wxT('\n'):
2035         c = wxT('n');
2036         break;
2037 
2038       case wxT('\r'):
2039         c = wxT('r');
2040         break;
2041 
2042       case wxT('\t'):
2043         c = wxT('t');
2044         break;
2045 
2046       case wxT('\\'):
2047         c = wxT('\\');
2048         break;
2049 
2050       case wxT('"'):
2051         if ( bQuote ) {
2052           c = wxT('"');
2053           break;
2054         }
2055         //else: fall through
2056 
2057       default:
2058         strResult += str[n];
2059         continue;   // nothing special to do
2060     }
2061 
2062     // we get here only for special characters
2063     strResult << wxT('\\') << c;
2064   }
2065 
2066   if ( bQuote )
2067     strResult += wxT('"');
2068 
2069   return strResult;
2070 }
2071 
2072 // undo FilterOutEntryName
FilterInEntryName(const wxString & str)2073 static wxString FilterInEntryName(const wxString& str)
2074 {
2075   wxString strResult;
2076   strResult.Alloc(str.Len());
2077 
2078   for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
2079     if ( *pc == wxT('\\') ) {
2080       // we need to test it here or we'd skip past the NUL in the loop line
2081       if ( *++pc == _T('\0') )
2082         break;
2083     }
2084 
2085     strResult += *pc;
2086   }
2087 
2088   return strResult;
2089 }
2090 
2091 // sanitize entry or group name: insert '\\' before any special characters
FilterOutEntryName(const wxString & str)2092 static wxString FilterOutEntryName(const wxString& str)
2093 {
2094   wxString strResult;
2095   strResult.Alloc(str.Len());
2096 
2097   for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
2098     const wxChar c = *pc;
2099 
2100     // we explicitly allow some of "safe" chars and 8bit ASCII characters
2101     // which will probably never have special meaning and with which we can't
2102     // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2103     //
2104     // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2105     //     should *not* be quoted
2106     if (
2107 #if !wxUSE_UNICODE
2108             ((unsigned char)c < 127) &&
2109 #endif // ANSI
2110          !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) )
2111     {
2112       strResult += wxT('\\');
2113     }
2114 
2115     strResult += c;
2116   }
2117 
2118   return strResult;
2119 }
2120 
2121 // we can't put ?: in the ctor initializer list because it confuses some
2122 // broken compilers (Borland C++)
GetAppName(const wxString & appName)2123 static wxString GetAppName(const wxString& appName)
2124 {
2125     if ( !appName && wxTheApp )
2126         return wxTheApp->GetAppName();
2127     else
2128         return appName;
2129 }
2130 
2131 #endif // wxUSE_CONFIG
2132