]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
arbitrary characters allowed in wxFileConfig entry names
[wxWidgets.git] / src / common / fileconf.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: 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$
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
12
13 #ifdef __GNUG__
14 #pragma implementation "fileconf.h"
15 #endif
16
17 // ============================================================================
18 // declarations
19 // ============================================================================
20
21 // ----------------------------------------------------------------------------
22 // headers
23 // ----------------------------------------------------------------------------
24 #include "wx/wxprec.h"
25
26 #ifdef __BORLANDC__
27 #pragma hdrstop
28 #endif //__BORLANDC__
29
30 #ifndef WX_PRECOMP
31 #include "wx/string.h"
32 #include "wx/intl.h"
33 #endif //WX_PRECOMP
34
35 #include "wx/app.h"
36 #include "wx/dynarray.h"
37 #include "wx/file.h"
38 #include "wx/log.h"
39 #include "wx/textfile.h"
40 #include "wx/config.h"
41 #include "wx/fileconf.h"
42
43 #include "wx/utils.h" // for wxGetHomeDir
44
45 // _WINDOWS_ is defined when windows.h is included,
46 // __WXMSW__ is defined for MS Windows compilation
47 #if defined(__WXMSW__) && !defined(_WINDOWS_)
48 #include <windows.h>
49 #endif //windows.h
50
51 #include <stdlib.h>
52 #include <ctype.h>
53
54 // ----------------------------------------------------------------------------
55 // macros
56 // ----------------------------------------------------------------------------
57 #define CONST_CAST ((wxFileConfig *)this)->
58
59 // ----------------------------------------------------------------------------
60 // constants
61 // ----------------------------------------------------------------------------
62 #ifndef MAX_PATH
63 #define MAX_PATH 512
64 #endif
65
66 // ----------------------------------------------------------------------------
67 // global functions declarations
68 // ----------------------------------------------------------------------------
69
70 // compare functions for sorting the arrays
71 static int CompareEntries(ConfigEntry *p1, ConfigEntry *p2);
72 static int CompareGroups(ConfigGroup *p1, ConfigGroup *p2);
73
74 // filter strings
75 static wxString FilterInValue(const wxString& str);
76 static wxString FilterOutValue(const wxString& str);
77
78 static wxString FilterInEntryName(const wxString& str);
79 static wxString FilterOutEntryName(const wxString& str);
80
81 // ============================================================================
82 // implementation
83 // ============================================================================
84
85 // ----------------------------------------------------------------------------
86 // static functions
87 // ----------------------------------------------------------------------------
88 wxString wxFileConfig::GetGlobalDir()
89 {
90 wxString strDir;
91
92 #ifdef __UNIX__
93 strDir = "/etc/";
94 #elif defined(__WXSTUBS__)
95 wxASSERT_MSG( FALSE, "TODO" ) ;
96 #elif defined(__WXMAC__)
97 wxASSERT_MSG( FALSE, "TODO" ) ;
98 #else // Windows
99 char szWinDir[MAX_PATH];
100 ::GetWindowsDirectory(szWinDir, MAX_PATH);
101
102 strDir = szWinDir;
103 strDir << '\\';
104 #endif // Unix/Windows
105
106 return strDir;
107 }
108
109 wxString wxFileConfig::GetLocalDir()
110 {
111 wxString strDir;
112
113 wxGetHomeDir(&strDir);
114
115 #ifdef __UNIX__
116 if (strDir.Last() != '/') strDir << '/';
117 #else
118 if (strDir.Last() != '\\') strDir << '\\';
119 #endif
120
121 return strDir;
122 }
123
124 wxString wxFileConfig::GetGlobalFileName(const char *szFile)
125 {
126 wxString str = GetGlobalDir();
127 str << szFile;
128
129 if ( strchr(szFile, '.') == NULL )
130 #ifdef __UNIX__
131 str << ".conf";
132 #else // Windows
133 str << ".ini";
134 #endif // UNIX/Win
135
136 return str;
137 }
138
139 wxString wxFileConfig::GetLocalFileName(const char *szFile)
140 {
141 wxString str = GetLocalDir();
142
143 #ifdef __UNIX__
144 str << '.';
145 #endif
146
147 str << szFile;
148
149 #ifdef __WXMSW__
150 if ( strchr(szFile, '.') == NULL )
151 str << ".ini";
152 #endif
153
154 return str;
155 }
156
157 // ----------------------------------------------------------------------------
158 // ctor
159 // ----------------------------------------------------------------------------
160
161 void wxFileConfig::Init()
162 {
163 m_pCurrentGroup =
164 m_pRootGroup = new ConfigGroup(NULL, "", this);
165
166 m_linesHead =
167 m_linesTail = NULL;
168
169 // it's not an error if (one of the) file(s) doesn't exist
170
171 // parse the global file
172 if ( !m_strGlobalFile.IsEmpty() && wxFile::Exists(m_strGlobalFile) ) {
173 wxTextFile fileGlobal(m_strGlobalFile);
174
175 if ( fileGlobal.Open() ) {
176 Parse(fileGlobal, FALSE /* global */);
177 SetRootPath();
178 }
179 else
180 wxLogWarning(_("can't open global configuration file '%s'."),
181 m_strGlobalFile.c_str());
182 }
183
184 // parse the local file
185 if ( !m_strLocalFile.IsEmpty() && wxFile::Exists(m_strLocalFile) ) {
186 wxTextFile fileLocal(m_strLocalFile);
187 if ( fileLocal.Open() ) {
188 Parse(fileLocal, TRUE /* local */);
189 SetRootPath();
190 }
191 else
192 wxLogWarning(_("can't open user configuration file '%s'."),
193 m_strLocalFile.c_str());
194 }
195 }
196
197 // constructor supports creation of wxFileConfig objects of any type
198 wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName,
199 const wxString& strLocal, const wxString& strGlobal,
200 long style)
201 : wxConfigBase(appName, vendorName, strLocal, strGlobal, style),
202 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
203 {
204 // Make up an application name if not supplied
205 if (appName.IsEmpty() && wxTheApp)
206 {
207 SetAppName(wxTheApp->GetAppName());
208 }
209
210 // Make up names for files if empty
211 if ( m_strLocalFile.IsEmpty() && (style & wxCONFIG_USE_LOCAL_FILE) )
212 {
213 m_strLocalFile = GetLocalFileName(GetAppName());
214 }
215
216 if ( m_strGlobalFile.IsEmpty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
217 {
218 m_strGlobalFile = GetGlobalFileName(GetAppName());
219 }
220
221 // Check if styles are not supplied, but filenames are, in which case
222 // add the correct styles.
223 if ( !m_strLocalFile.IsEmpty() )
224 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
225
226 if ( !m_strGlobalFile.IsEmpty() )
227 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
228
229 // if the path is not absolute, prepend the standard directory to it
230 if ( !m_strLocalFile.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile) )
231 {
232 wxString strLocal = m_strLocalFile;
233 m_strLocalFile = GetLocalDir();
234 m_strLocalFile << strLocal;
235 }
236
237 if ( !m_strGlobalFile.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile) )
238 {
239 wxString strGlobal = m_strGlobalFile;
240 m_strGlobalFile = GetGlobalDir();
241 m_strGlobalFile << strGlobal;
242 }
243
244 Init();
245 }
246
247 void wxFileConfig::CleanUp()
248 {
249 delete m_pRootGroup;
250
251 LineList *pCur = m_linesHead;
252 while ( pCur != NULL ) {
253 LineList *pNext = pCur->Next();
254 delete pCur;
255 pCur = pNext;
256 }
257 }
258
259 wxFileConfig::~wxFileConfig()
260 {
261 Flush();
262
263 CleanUp();
264 }
265
266 // ----------------------------------------------------------------------------
267 // parse a config file
268 // ----------------------------------------------------------------------------
269
270 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
271 {
272 const char *pStart;
273 const char *pEnd;
274 wxString strLine;
275
276 size_t nLineCount = file.GetLineCount();
277 for ( size_t n = 0; n < nLineCount; n++ ) {
278 strLine = file[n];
279
280 // add the line to linked list
281 if ( bLocal )
282 LineListAppend(strLine);
283
284 // skip leading spaces
285 for ( pStart = strLine; isspace(*pStart); pStart++ )
286 ;
287
288 // skip blank/comment lines
289 if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' )
290 continue;
291
292 if ( *pStart == '[' ) { // a new group
293 pEnd = pStart;
294
295 while ( *++pEnd != ']' ) {
296 if ( *pEnd == '\n' || *pEnd == '\0' )
297 break;
298 }
299
300 if ( *pEnd != ']' ) {
301 wxLogError(_("file '%s': unexpected character %c at line %d."),
302 file.GetName(), *pEnd, n + 1);
303 continue; // skip this line
304 }
305
306 // group name here is always considered as abs path
307 wxString strGroup;
308 pStart++;
309 strGroup << wxCONFIG_PATH_SEPARATOR
310 << FilterInEntryName(wxString(pStart, pEnd - pStart));
311
312 // will create it if doesn't yet exist
313 SetPath(strGroup);
314
315 if ( bLocal )
316 m_pCurrentGroup->SetLine(m_linesTail);
317
318 // check that there is nothing except comments left on this line
319 bool bCont = TRUE;
320 while ( *++pEnd != '\0' && bCont ) {
321 switch ( *pEnd ) {
322 case '#':
323 case ';':
324 bCont = FALSE;
325 break;
326
327 case ' ':
328 case '\t':
329 // ignore whitespace ('\n' impossible here)
330 break;
331
332 default:
333 wxLogWarning(_("file '%s', line %d: '%s' "
334 "ignored after group header."),
335 file.GetName(), n + 1, pEnd);
336 bCont = FALSE;
337 }
338 }
339 }
340 else { // a key
341 const char *pEnd = pStart;
342 while ( !isspace(*pEnd) ) {
343 if ( *pEnd == '\\' ) {
344 // next character may be space or not - still take it because it's
345 // quoted
346 pEnd++;
347 }
348
349 pEnd++;
350 }
351
352 wxString strKey(FilterInEntryName(wxString(pStart, pEnd)));
353
354 // skip whitespace
355 while ( isspace(*pEnd) )
356 pEnd++;
357
358 if ( *pEnd++ != '=' ) {
359 wxLogError(_("file '%s', line %d: '=' expected."),
360 file.GetName(), n + 1);
361 }
362 else {
363 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
364
365 if ( pEntry == NULL ) {
366 // new entry
367 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
368
369 if ( bLocal )
370 pEntry->SetLine(m_linesTail);
371 }
372 else {
373 if ( bLocal && pEntry->IsImmutable() ) {
374 // immutable keys can't be changed by user
375 wxLogWarning(_("file '%s', line %d: value for "
376 "immutable key '%s' ignored."),
377 file.GetName(), n + 1, strKey.c_str());
378 continue;
379 }
380 // the condition below catches the cases (a) and (b) but not (c):
381 // (a) global key found second time in global file
382 // (b) key found second (or more) time in local file
383 // (c) key from global file now found in local one
384 // which is exactly what we want.
385 else if ( !bLocal || pEntry->IsLocal() ) {
386 wxLogWarning(_("file '%s', line %d: key '%s' was first "
387 "found at line %d."),
388 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
389
390 if ( bLocal )
391 pEntry->SetLine(m_linesTail);
392 }
393 }
394
395 // skip whitespace
396 while ( isspace(*pEnd) )
397 pEnd++;
398
399 pEntry->SetValue(FilterInValue(pEnd), FALSE /* read from file */);
400 }
401 }
402 }
403 }
404
405 // ----------------------------------------------------------------------------
406 // set/retrieve path
407 // ----------------------------------------------------------------------------
408
409 void wxFileConfig::SetRootPath()
410 {
411 m_strPath.Empty();
412 m_pCurrentGroup = m_pRootGroup;
413 }
414
415 void wxFileConfig::SetPath(const wxString& strPath)
416 {
417 wxArrayString aParts;
418
419 if ( strPath.IsEmpty() ) {
420 SetRootPath();
421 return;
422 }
423
424 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
425 // absolute path
426 wxSplitPath(aParts, strPath);
427 }
428 else {
429 // relative path, combine with current one
430 wxString strFullPath = m_strPath;
431 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
432 wxSplitPath(aParts, strFullPath);
433 }
434
435 // change current group
436 size_t n;
437 m_pCurrentGroup = m_pRootGroup;
438 for ( n = 0; n < aParts.Count(); n++ ) {
439 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
440 if ( pNextGroup == NULL )
441 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
442 m_pCurrentGroup = pNextGroup;
443 }
444
445 // recombine path parts in one variable
446 m_strPath.Empty();
447 for ( n = 0; n < aParts.Count(); n++ ) {
448 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
449 }
450 }
451
452 // ----------------------------------------------------------------------------
453 // enumeration
454 // ----------------------------------------------------------------------------
455
456 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
457 {
458 lIndex = 0;
459 return GetNextGroup(str, lIndex);
460 }
461
462 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
463 {
464 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
465 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
466 return TRUE;
467 }
468 else
469 return FALSE;
470 }
471
472 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
473 {
474 lIndex = 0;
475 return GetNextEntry(str, lIndex);
476 }
477
478 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
479 {
480 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
481 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
482 return TRUE;
483 }
484 else
485 return FALSE;
486 }
487
488 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
489 {
490 size_t n = m_pCurrentGroup->Entries().Count();
491 if ( bRecursive ) {
492 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
493 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
494 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
495 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
496 n += GetNumberOfEntries(TRUE);
497 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
498 }
499 }
500
501 return n;
502 }
503
504 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
505 {
506 size_t n = m_pCurrentGroup->Groups().Count();
507 if ( bRecursive ) {
508 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
509 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
510 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
511 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
512 n += GetNumberOfGroups(TRUE);
513 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
514 }
515 }
516
517 return n;
518 }
519
520 // ----------------------------------------------------------------------------
521 // tests for existence
522 // ----------------------------------------------------------------------------
523
524 bool wxFileConfig::HasGroup(const wxString& strName) const
525 {
526 wxConfigPathChanger path(this, strName);
527
528 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
529 return pGroup != NULL;
530 }
531
532 bool wxFileConfig::HasEntry(const wxString& strName) const
533 {
534 wxConfigPathChanger path(this, strName);
535
536 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
537 return pEntry != NULL;
538 }
539
540 // ----------------------------------------------------------------------------
541 // read/write values
542 // ----------------------------------------------------------------------------
543
544 bool wxFileConfig::Read(const wxString& key,
545 wxString* pStr) const
546 {
547 wxConfigPathChanger path(this, key);
548
549 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
550 if (pEntry == NULL) {
551 return FALSE;
552 }
553 else {
554 *pStr = ExpandEnvVars(pEntry->Value());
555 return TRUE;
556 }
557 }
558
559 bool wxFileConfig::Read(const wxString& key,
560 wxString* pStr, const wxString& defVal) const
561 {
562 wxConfigPathChanger path(this, key);
563
564 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
565 if (pEntry == NULL) {
566 if( IsRecordingDefaults() )
567 ((wxFileConfig *)this)->Write(key,defVal);
568 *pStr = ExpandEnvVars(defVal);
569 return FALSE;
570 }
571 else {
572 *pStr = ExpandEnvVars(pEntry->Value());
573 return TRUE;
574 }
575 }
576
577 bool wxFileConfig::Read(const wxString& key, long *pl) const
578 {
579 wxString str;
580 if ( Read(key, & str) ) {
581 *pl = atol(str);
582 return TRUE;
583 }
584 else {
585 return FALSE;
586 }
587 }
588
589 bool wxFileConfig::Write(const wxString& key, const wxString& szValue)
590 {
591 wxConfigPathChanger path(this, key);
592
593 wxString strName = path.Name();
594 if ( strName.IsEmpty() ) {
595 // setting the value of a group is an error
596 wxASSERT_MSG( IsEmpty(szValue), "can't set value of a group!" );
597
598 // ... except if it's empty in which case it's a way to force it's creation
599 m_pCurrentGroup->SetDirty();
600
601 // this will add a line for this group if it didn't have it before
602 (void)m_pCurrentGroup->GetGroupLine();
603 }
604 else {
605 // writing an entry
606
607 // check that the name is reasonable
608 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
609 wxLogError(_("Config entry name cannot start with '%c'."),
610 wxCONFIG_IMMUTABLE_PREFIX);
611 return FALSE;
612 }
613
614 strName = FilterOutEntryName(strName);
615
616 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
617 if ( pEntry == NULL )
618 pEntry = m_pCurrentGroup->AddEntry(strName);
619
620 pEntry->SetValue(szValue);
621 }
622
623 return TRUE;
624 }
625
626 bool wxFileConfig::Write(const wxString& key, long lValue)
627 {
628 // ltoa() is not ANSI :-(
629 wxString buf;
630 buf.Printf("%ld", lValue);
631 return Write(key, buf);
632 }
633
634 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
635 {
636 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
637 return TRUE;
638
639 wxTempFile file(m_strLocalFile);
640
641 if ( !file.IsOpened() ) {
642 wxLogError(_("can't open user configuration file."));
643 return FALSE;
644 }
645
646 // write all strings to file
647 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
648 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
649 wxLogError(_("can't write user configuration file."));
650 return FALSE;
651 }
652 }
653
654 return file.Commit();
655 }
656
657 // ----------------------------------------------------------------------------
658 // renaming groups/entries
659 // ----------------------------------------------------------------------------
660
661 bool wxFileConfig::RenameEntry(const wxString& oldName,
662 const wxString& newName)
663 {
664 // check that the entry exists
665 ConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
666 if ( !oldEntry )
667 return FALSE;
668
669 // check that the new entry doesn't already exist
670 if ( m_pCurrentGroup->FindEntry(newName) )
671 return FALSE;
672
673 // delete the old entry, create the new one
674 wxString value = oldEntry->Value();
675 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
676 return FALSE;
677
678 ConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
679 newEntry->SetValue(value);
680
681 return TRUE;
682 }
683
684 bool wxFileConfig::RenameGroup(const wxString& oldName,
685 const wxString& newName)
686 {
687 // check that the group exists
688 ConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
689 if ( !group )
690 return FALSE;
691
692 // check that the new group doesn't already exist
693 if ( m_pCurrentGroup->FindSubgroup(newName) )
694 return FALSE;
695
696 group->Rename(newName);
697
698 return TRUE;
699 }
700
701 // ----------------------------------------------------------------------------
702 // delete groups/entries
703 // ----------------------------------------------------------------------------
704
705 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
706 {
707 wxConfigPathChanger path(this, key);
708
709 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
710 return FALSE;
711
712 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
713 if ( m_pCurrentGroup != m_pRootGroup ) {
714 ConfigGroup *pGroup = m_pCurrentGroup;
715 SetPath(".."); // changes m_pCurrentGroup!
716 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
717 }
718 //else: never delete the root group
719 }
720
721 return TRUE;
722 }
723
724 bool wxFileConfig::DeleteGroup(const wxString& key)
725 {
726 wxConfigPathChanger path(this, key);
727
728 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
729 }
730
731 bool wxFileConfig::DeleteAll()
732 {
733 CleanUp();
734
735 const char *szFile = m_strLocalFile;
736
737 if ( remove(szFile) == -1 )
738 wxLogSysError(_("can't delete user configuration file '%s'"), szFile);
739
740 m_strLocalFile = m_strGlobalFile = "";
741 Init();
742
743 return TRUE;
744 }
745
746 // ----------------------------------------------------------------------------
747 // linked list functions
748 // ----------------------------------------------------------------------------
749
750 // append a new line to the end of the list
751 LineList *wxFileConfig::LineListAppend(const wxString& str)
752 {
753 LineList *pLine = new LineList(str);
754
755 if ( m_linesTail == NULL ) {
756 // list is empty
757 m_linesHead = pLine;
758 }
759 else {
760 // adjust pointers
761 m_linesTail->SetNext(pLine);
762 pLine->SetPrev(m_linesTail);
763 }
764
765 m_linesTail = pLine;
766 return m_linesTail;
767 }
768
769 // insert a new line after the given one or in the very beginning if !pLine
770 LineList *wxFileConfig::LineListInsert(const wxString& str,
771 LineList *pLine)
772 {
773 if ( pLine == m_linesTail )
774 return LineListAppend(str);
775
776 LineList *pNewLine = new LineList(str);
777 if ( pLine == NULL ) {
778 // prepend to the list
779 pNewLine->SetNext(m_linesHead);
780 m_linesHead->SetPrev(pNewLine);
781 m_linesHead = pNewLine;
782 }
783 else {
784 // insert before pLine
785 LineList *pNext = pLine->Next();
786 pNewLine->SetNext(pNext);
787 pNewLine->SetPrev(pLine);
788 pNext->SetPrev(pNewLine);
789 pLine->SetNext(pNewLine);
790 }
791
792 return pNewLine;
793 }
794
795 void wxFileConfig::LineListRemove(LineList *pLine)
796 {
797 LineList *pPrev = pLine->Prev(),
798 *pNext = pLine->Next();
799
800 // first entry?
801 if ( pPrev == NULL )
802 m_linesHead = pNext;
803 else
804 pPrev->SetNext(pNext);
805
806 // last entry?
807 if ( pNext == NULL )
808 m_linesTail = pPrev;
809 else
810 pNext->SetPrev(pPrev);
811
812 delete pLine;
813 }
814
815 bool wxFileConfig::LineListIsEmpty()
816 {
817 return m_linesHead == NULL;
818 }
819
820 // ============================================================================
821 // wxFileConfig::ConfigGroup
822 // ============================================================================
823
824 // ----------------------------------------------------------------------------
825 // ctor/dtor
826 // ----------------------------------------------------------------------------
827
828 // ctor
829 ConfigGroup::ConfigGroup(ConfigGroup *pParent,
830 const wxString& strName,
831 wxFileConfig *pConfig)
832 : m_aEntries(CompareEntries),
833 m_aSubgroups(CompareGroups),
834 m_strName(strName)
835 {
836 m_pConfig = pConfig;
837 m_pParent = pParent;
838 m_bDirty = FALSE;
839 m_pLine = NULL;
840
841 m_pLastEntry = NULL;
842 m_pLastGroup = NULL;
843 }
844
845 // dtor deletes all children
846 ConfigGroup::~ConfigGroup()
847 {
848 // entries
849 size_t n, nCount = m_aEntries.Count();
850 for ( n = 0; n < nCount; n++ )
851 delete m_aEntries[n];
852
853 // subgroups
854 nCount = m_aSubgroups.Count();
855 for ( n = 0; n < nCount; n++ )
856 delete m_aSubgroups[n];
857 }
858
859 // ----------------------------------------------------------------------------
860 // line
861 // ----------------------------------------------------------------------------
862
863 void ConfigGroup::SetLine(LineList *pLine)
864 {
865 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
866
867 m_pLine = pLine;
868 }
869
870 /*
871 This is a bit complicated, so let me explain it in details. All lines that
872 were read from the local file (the only one we will ever modify) are stored
873 in a (doubly) linked list. Our problem is to know at which position in this
874 list should we insert the new entries/subgroups. To solve it we keep three
875 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
876
877 m_pLine points to the line containing "[group_name]"
878 m_pLastEntry points to the last entry of this group in the local file.
879 m_pLastGroup subgroup
880
881 Initially, they're NULL all three. When the group (an entry/subgroup) is read
882 from the local file, the corresponding variable is set. However, if the group
883 was read from the global file and then modified or created by the application
884 these variables are still NULL and we need to create the corresponding lines.
885 See the following functions (and comments preceding them) for the details of
886 how we do it.
887
888 Also, when our last entry/group are deleted we need to find the new last
889 element - the code in DeleteEntry/Subgroup does this by backtracking the list
890 of lines until it either founds an entry/subgroup (and this is the new last
891 element) or the m_pLine of the group, in which case there are no more entries
892 (or subgroups) left and m_pLast<element> becomes NULL.
893
894 NB: This last problem could be avoided for entries if we added new entries
895 immediately after m_pLine, but in this case the entries would appear
896 backwards in the config file (OTOH, it's not that important) and as we
897 would still need to do it for the subgroups the code wouldn't have been
898 significantly less complicated.
899 */
900
901 // Return the line which contains "[our name]". If we're still not in the list,
902 // add our line to it immediately after the last line of our parent group if we
903 // have it or in the very beginning if we're the root group.
904 LineList *ConfigGroup::GetGroupLine()
905 {
906 if ( m_pLine == NULL ) {
907 ConfigGroup *pParent = Parent();
908
909 // this group wasn't present in local config file, add it now
910 if ( pParent != NULL ) {
911 wxString strFullName;
912 strFullName << "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
913 m_pLine = m_pConfig->LineListInsert(strFullName,
914 pParent->GetLastGroupLine());
915 pParent->SetLastGroup(this); // we're surely after all the others
916 }
917 else {
918 // we return NULL, so that LineListInsert() will insert us in the
919 // very beginning
920 }
921 }
922
923 return m_pLine;
924 }
925
926 // Return the last line belonging to the subgroups of this group (after which
927 // we can add a new subgroup), if we don't have any subgroups or entries our
928 // last line is the group line (m_pLine) itself.
929 LineList *ConfigGroup::GetLastGroupLine()
930 {
931 // if we have any subgroups, our last line is the last line of the last
932 // subgroup
933 if ( m_pLastGroup != NULL ) {
934 LineList *pLine = m_pLastGroup->GetLastGroupLine();
935
936 wxASSERT( pLine != NULL ); // last group must have !NULL associated line
937 return pLine;
938 }
939
940 // no subgroups, so the last line is the line of thelast entry (if any)
941 return GetLastEntryLine();
942 }
943
944 // return the last line belonging to the entries of this group (after which
945 // we can add a new entry), if we don't have any entries we will add the new
946 // one immediately after the group line itself.
947 LineList *ConfigGroup::GetLastEntryLine()
948 {
949 if ( m_pLastEntry != NULL ) {
950 LineList *pLine = m_pLastEntry->GetLine();
951
952 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
953 return pLine;
954 }
955
956 // no entries: insert after the group header
957 return GetGroupLine();
958 }
959
960 // ----------------------------------------------------------------------------
961 // group name
962 // ----------------------------------------------------------------------------
963
964 void ConfigGroup::Rename(const wxString& newName)
965 {
966 m_strName = newName;
967
968 LineList *line = GetGroupLine();
969 wxString strFullName;
970 strFullName << "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
971 line->SetText(strFullName);
972
973 SetDirty();
974 }
975
976 wxString ConfigGroup::GetFullName() const
977 {
978 if ( Parent() )
979 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
980 else
981 return "";
982 }
983
984 // ----------------------------------------------------------------------------
985 // find an item
986 // ----------------------------------------------------------------------------
987
988 // use binary search because the array is sorted
989 ConfigEntry *
990 ConfigGroup::FindEntry(const char *szName) const
991 {
992 size_t i,
993 lo = 0,
994 hi = m_aEntries.Count();
995 int res;
996 ConfigEntry *pEntry;
997
998 while ( lo < hi ) {
999 i = (lo + hi)/2;
1000 pEntry = m_aEntries[i];
1001
1002 #if wxCONFIG_CASE_SENSITIVE
1003 res = strcmp(pEntry->Name(), szName);
1004 #else
1005 res = Stricmp(pEntry->Name(), szName);
1006 #endif
1007
1008 if ( res > 0 )
1009 hi = i;
1010 else if ( res < 0 )
1011 lo = i + 1;
1012 else
1013 return pEntry;
1014 }
1015
1016 return NULL;
1017 }
1018
1019 ConfigGroup *
1020 ConfigGroup::FindSubgroup(const char *szName) const
1021 {
1022 size_t i,
1023 lo = 0,
1024 hi = m_aSubgroups.Count();
1025 int res;
1026 ConfigGroup *pGroup;
1027
1028 while ( lo < hi ) {
1029 i = (lo + hi)/2;
1030 pGroup = m_aSubgroups[i];
1031
1032 #if wxCONFIG_CASE_SENSITIVE
1033 res = strcmp(pGroup->Name(), szName);
1034 #else
1035 res = Stricmp(pGroup->Name(), szName);
1036 #endif
1037
1038 if ( res > 0 )
1039 hi = i;
1040 else if ( res < 0 )
1041 lo = i + 1;
1042 else
1043 return pGroup;
1044 }
1045
1046 return NULL;
1047 }
1048
1049 // ----------------------------------------------------------------------------
1050 // create a new item
1051 // ----------------------------------------------------------------------------
1052
1053 // create a new entry and add it to the current group
1054 ConfigEntry *
1055 ConfigGroup::AddEntry(const wxString& strName, int nLine)
1056 {
1057 wxASSERT( FindEntry(strName) == NULL );
1058
1059 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
1060 m_aEntries.Add(pEntry);
1061
1062 return pEntry;
1063 }
1064
1065 // create a new group and add it to the current group
1066 ConfigGroup *
1067 ConfigGroup::AddSubgroup(const wxString& strName)
1068 {
1069 wxASSERT( FindSubgroup(strName) == NULL );
1070
1071 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
1072 m_aSubgroups.Add(pGroup);
1073
1074 return pGroup;
1075 }
1076
1077 // ----------------------------------------------------------------------------
1078 // delete an item
1079 // ----------------------------------------------------------------------------
1080
1081 /*
1082 The delete operations are _very_ slow if we delete the last item of this
1083 group (see comments before GetXXXLineXXX functions for more details),
1084 so it's much better to start with the first entry/group if we want to
1085 delete several of them.
1086 */
1087
1088 bool ConfigGroup::DeleteSubgroupByName(const char *szName)
1089 {
1090 return DeleteSubgroup(FindSubgroup(szName));
1091 }
1092
1093 // doesn't delete the subgroup itself, but does remove references to it from
1094 // all other data structures (and normally the returned pointer should be
1095 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1096 bool ConfigGroup::DeleteSubgroup(ConfigGroup *pGroup)
1097 {
1098 wxCHECK( pGroup != NULL, FALSE ); // deleting non existing group?
1099
1100 // delete all entries
1101 size_t nCount = pGroup->m_aEntries.Count();
1102 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ ) {
1103 LineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1104 if ( pLine != NULL )
1105 m_pConfig->LineListRemove(pLine);
1106 }
1107
1108 // and subgroups of this sungroup
1109 nCount = pGroup->m_aSubgroups.Count();
1110 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ ) {
1111 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[nGroup]);
1112 }
1113
1114 LineList *pLine = pGroup->m_pLine;
1115 if ( pLine != NULL ) {
1116 // notice that we may do this test inside the previous "if" because the
1117 // last entry's line is surely !NULL
1118 if ( pGroup == m_pLastGroup ) {
1119 // our last entry is being deleted - find the last one which stays
1120 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
1121
1122 // go back until we find a subgroup or reach the group's line
1123 ConfigGroup *pNewLast = NULL;
1124 size_t n, nSubgroups = m_aSubgroups.Count();
1125 LineList *pl;
1126 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1127 // is it our subgroup?
1128 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
1129 // do _not_ call GetGroupLine! we don't want to add it to the local
1130 // file if it's not already there
1131 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1132 pNewLast = m_aSubgroups[n];
1133 }
1134
1135 if ( pNewLast != NULL ) // found?
1136 break;
1137 }
1138
1139 if ( pl == m_pLine ) {
1140 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1141
1142 // we've reached the group line without finding any subgroups
1143 m_pLastGroup = NULL;
1144 }
1145 else
1146 m_pLastGroup = pNewLast;
1147 }
1148
1149 m_pConfig->LineListRemove(pLine);
1150 }
1151
1152 SetDirty();
1153
1154 m_aSubgroups.Remove(pGroup);
1155 delete pGroup;
1156
1157 return TRUE;
1158 }
1159
1160 bool ConfigGroup::DeleteEntry(const char *szName)
1161 {
1162 ConfigEntry *pEntry = FindEntry(szName);
1163 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1164
1165 LineList *pLine = pEntry->GetLine();
1166 if ( pLine != NULL ) {
1167 // notice that we may do this test inside the previous "if" because the
1168 // last entry's line is surely !NULL
1169 if ( pEntry == m_pLastEntry ) {
1170 // our last entry is being deleted - find the last one which stays
1171 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1172
1173 // go back until we find another entry or reach the group's line
1174 ConfigEntry *pNewLast = NULL;
1175 size_t n, nEntries = m_aEntries.Count();
1176 LineList *pl;
1177 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1178 // is it our subgroup?
1179 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1180 if ( m_aEntries[n]->GetLine() == m_pLine )
1181 pNewLast = m_aEntries[n];
1182 }
1183
1184 if ( pNewLast != NULL ) // found?
1185 break;
1186 }
1187
1188 if ( pl == m_pLine ) {
1189 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1190
1191 // we've reached the group line without finding any subgroups
1192 m_pLastEntry = NULL;
1193 }
1194 else
1195 m_pLastEntry = pNewLast;
1196 }
1197
1198 m_pConfig->LineListRemove(pLine);
1199 }
1200
1201 // we must be written back for the changes to be saved
1202 SetDirty();
1203
1204 m_aEntries.Remove(pEntry);
1205 delete pEntry;
1206
1207 return TRUE;
1208 }
1209
1210 // ----------------------------------------------------------------------------
1211 //
1212 // ----------------------------------------------------------------------------
1213 void ConfigGroup::SetDirty()
1214 {
1215 m_bDirty = TRUE;
1216 if ( Parent() != NULL ) // propagate upwards
1217 Parent()->SetDirty();
1218 }
1219
1220 // ============================================================================
1221 // wxFileConfig::ConfigEntry
1222 // ============================================================================
1223
1224 // ----------------------------------------------------------------------------
1225 // ctor
1226 // ----------------------------------------------------------------------------
1227 ConfigEntry::ConfigEntry(ConfigGroup *pParent,
1228 const wxString& strName,
1229 int nLine)
1230 : m_strName(strName)
1231 {
1232 wxASSERT( !strName.IsEmpty() );
1233
1234 m_pParent = pParent;
1235 m_nLine = nLine;
1236 m_pLine = NULL;
1237
1238 m_bDirty = FALSE;
1239
1240 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1241 if ( m_bImmutable )
1242 m_strName.erase(0, 1); // remove first character
1243 }
1244
1245 // ----------------------------------------------------------------------------
1246 // set value
1247 // ----------------------------------------------------------------------------
1248
1249 void ConfigEntry::SetLine(LineList *pLine)
1250 {
1251 if ( m_pLine != NULL ) {
1252 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1253 Name().c_str(), m_pParent->GetFullName().c_str());
1254 }
1255
1256 m_pLine = pLine;
1257 Group()->SetLastEntry(this);
1258 }
1259
1260 // second parameter is FALSE if we read the value from file and prevents the
1261 // entry from being marked as 'dirty'
1262 void ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1263 {
1264 if ( bUser && IsImmutable() ) {
1265 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1266 Name().c_str());
1267 return;
1268 }
1269
1270 // do nothing if it's the same value
1271 if ( strValue == m_strValue )
1272 return;
1273
1274 m_strValue = strValue;
1275
1276 if ( bUser ) {
1277 wxString strVal = FilterOutValue(strValue);
1278 wxString strLine;
1279 strLine << m_strName << " = " << strVal;
1280
1281 if ( m_pLine != NULL ) {
1282 // entry was read from the local config file, just modify the line
1283 m_pLine->SetText(strLine);
1284 }
1285 else {
1286 // add a new line to the file
1287 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1288
1289 m_pLine = Group()->Config()->LineListInsert(strLine,
1290 Group()->GetLastEntryLine());
1291 Group()->SetLastEntry(this);
1292 }
1293
1294 SetDirty();
1295 }
1296 }
1297
1298 void ConfigEntry::SetDirty()
1299 {
1300 m_bDirty = TRUE;
1301 Group()->SetDirty();
1302 }
1303
1304 // ============================================================================
1305 // global functions
1306 // ============================================================================
1307
1308 // ----------------------------------------------------------------------------
1309 // compare functions for array sorting
1310 // ----------------------------------------------------------------------------
1311
1312 int CompareEntries(ConfigEntry *p1,
1313 ConfigEntry *p2)
1314 {
1315 #if wxCONFIG_CASE_SENSITIVE
1316 return strcmp(p1->Name(), p2->Name());
1317 #else
1318 return Stricmp(p1->Name(), p2->Name());
1319 #endif
1320 }
1321
1322 int CompareGroups(ConfigGroup *p1,
1323 ConfigGroup *p2)
1324 {
1325 #if wxCONFIG_CASE_SENSITIVE
1326 return strcmp(p1->Name(), p2->Name());
1327 #else
1328 return Stricmp(p1->Name(), p2->Name());
1329 #endif
1330 }
1331
1332 // ----------------------------------------------------------------------------
1333 // filter functions
1334 // ----------------------------------------------------------------------------
1335
1336 // undo FilterOutValue
1337 static wxString FilterInValue(const wxString& str)
1338 {
1339 wxString strResult;
1340 strResult.Alloc(str.Len());
1341
1342 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1343
1344 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1345 if ( str[n] == '\\' ) {
1346 switch ( str[++n] ) {
1347 case 'n':
1348 strResult += '\n';
1349 break;
1350
1351 case 'r':
1352 strResult += '\r';
1353 break;
1354
1355 case 't':
1356 strResult += '\t';
1357 break;
1358
1359 case '\\':
1360 strResult += '\\';
1361 break;
1362
1363 case '"':
1364 strResult += '"';
1365 break;
1366 }
1367 }
1368 else {
1369 if ( str[n] != '"' || !bQuoted )
1370 strResult += str[n];
1371 else if ( n != str.Len() - 1 ) {
1372 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1373 n, str.c_str());
1374 }
1375 //else: it's the last quote of a quoted string, ok
1376 }
1377 }
1378
1379 return strResult;
1380 }
1381
1382 // quote the string before writing it to file
1383 static wxString FilterOutValue(const wxString& str)
1384 {
1385 if ( !str )
1386 return str;
1387
1388 wxString strResult;
1389 strResult.Alloc(str.Len());
1390
1391 // quoting is necessary to preserve spaces in the beginning of the string
1392 bool bQuote = isspace(str[0]) || str[0] == '"';
1393
1394 if ( bQuote )
1395 strResult += '"';
1396
1397 char c;
1398 for ( size_t n = 0; n < str.Len(); n++ ) {
1399 switch ( str[n] ) {
1400 case '\n':
1401 c = 'n';
1402 break;
1403
1404 case '\r':
1405 c = 'r';
1406 break;
1407
1408 case '\t':
1409 c = 't';
1410 break;
1411
1412 case '\\':
1413 c = '\\';
1414 break;
1415
1416 case '"':
1417 if ( bQuote ) {
1418 c = '"';
1419 break;
1420 }
1421 //else: fall through
1422
1423 default:
1424 strResult += str[n];
1425 continue; // nothing special to do
1426 }
1427
1428 // we get here only for special characters
1429 strResult << '\\' << c;
1430 }
1431
1432 if ( bQuote )
1433 strResult += '"';
1434
1435 return strResult;
1436 }
1437
1438 // undo FilterOutEntryName
1439 static wxString FilterInEntryName(const wxString& str)
1440 {
1441 wxString strResult;
1442 strResult.Alloc(str.Len());
1443
1444 for ( const char *pc = str.c_str(); *pc != '\0'; pc++ ) {
1445 if ( *pc == '\\' )
1446 pc++;
1447
1448 strResult += *pc;
1449 }
1450
1451 return strResult;
1452 }
1453
1454 // sanitize entry or group name: insert '\\' before any special characters
1455 static wxString FilterOutEntryName(const wxString& str)
1456 {
1457 wxString strResult;
1458 strResult.Alloc(str.Len());
1459
1460 for ( const char *pc = str.c_str(); *pc != '\0'; pc++ ) {
1461 char c = *pc;
1462
1463 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1464 // which will probably never have special meaning
1465 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1466 // should *not* be quoted
1467 if ( !isalnum(c) && !strchr("@_/-!.*%", c) && ((c & 0x80) == 0) )
1468 strResult += '\\';
1469
1470 strResult += c;
1471 }
1472
1473 return strResult;
1474 }
1475