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