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