]>
git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
1 ///////////////////////////////////////////////////////////////////////////////
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
6 // Created: 07.04.98 (adapted from appconf.cpp)
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
14 #pragma implementation "fileconf.h"
17 // ============================================================================
19 // ============================================================================
21 // ----------------------------------------------------------------------------
23 // ----------------------------------------------------------------------------
24 #include "wx/wxprec.h"
31 #include <wx/string.h>
35 #include <wx/dynarray.h>
38 #include <wx/textfile.h>
39 #include <wx/config.h>
40 #include <wx/fileconf.h>
42 // _WINDOWS_ is defined when windows.h is included,
43 // __WINDOWS__ is defined for MS Windows compilation
44 #if defined(__WINDOWS__) && !defined(_WINDOWS_)
51 // ----------------------------------------------------------------------------
52 // global functions declarations
53 // ----------------------------------------------------------------------------
55 // is 'c' a valid character in group name?
56 // NB: APPCONF_IMMUTABLE_PREFIX and APPCONF_PATH_SEPARATOR must be valid chars,
57 // but _not_ ']' (group name delimiter)
58 inline bool IsValid(char c
) { return isalnum(c
) || strchr("_/-!.*%", c
); }
61 static wxString
FilterIn(const wxString
& str
);
62 static wxString
FilterOut(const wxString
& str
);
64 // ============================================================================
66 // ============================================================================
68 // ----------------------------------------------------------------------------
70 // ----------------------------------------------------------------------------
71 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
75 bool bNoExt
= strchr(szFile
, '.') == NULL
;
78 str
<< "/etc/" << szFile
;
86 char szWinDir
[_MAX_PATH
];
87 ::GetWindowsDirectory(szWinDir
, _MAX_PATH
);
88 str
<< szWinDir
<< "\\" << szFile
;
96 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
101 const char *szHome
= getenv("HOME");
102 if ( szHome
== NULL
) {
104 wxLogWarning("can't find user's HOME, using current directory.");
107 str
<< szHome
<< "/." << szFile
;
110 const char *szHome
= getenv("HOMEDRIVE");
111 if ( szHome
!= NULL
)
113 szHome
= getenv("HOMEPATH");
114 if ( szHome
!= NULL
)
117 if ( strchr(szFile
, '.') == NULL
)
120 // Win16 has no idea about home, so use the current directory instead
121 str
<< ".\\" << szFile
;
128 // ----------------------------------------------------------------------------
130 // ----------------------------------------------------------------------------
132 void wxFileConfig::Init()
135 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
140 m_bExpandEnvVars
= TRUE
;
145 wxFileConfig::wxFileConfig(const wxString
& strLocal
, const wxString
& strGlobal
)
146 : m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
150 // it's not an error if (one of the) file(s) doesn't exist
152 // parse the global file
153 if ( !strGlobal
.IsEmpty() ) {
154 if ( wxFile::Exists(strGlobal
) ) {
155 wxTextFile
fileGlobal(strGlobal
);
157 if ( fileGlobal
.Open() ) {
158 Parse(fileGlobal
, FALSE
/* global */);
162 wxLogWarning("Can't open global configuration file '%s'.",
167 // parse the local file
168 if ( wxFile::Exists(strLocal
) ) {
169 wxTextFile
fileLocal(strLocal
);
170 if ( fileLocal
.Open() ) {
171 Parse(fileLocal
, TRUE
/* local */);
175 wxLogWarning("Can't open user configuration file '%s'.",
180 wxFileConfig::~wxFileConfig()
186 // ----------------------------------------------------------------------------
187 // parse a config file
188 // ----------------------------------------------------------------------------
190 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
195 for ( uint n
= 0; n
< file
.GetLineCount(); n
++ ) {
196 // add the line to linked list
198 LineListAppend(file
[n
]);
200 // skip leading spaces
201 for ( pStart
= file
[n
]; isspace(*pStart
); pStart
++ )
204 // skip blank/comment lines
205 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
208 if ( *pStart
== '[' ) { // a new group
211 while ( *++pEnd
!= ']' ) {
212 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
216 if ( *pEnd
!= ']' ) {
217 wxLogError("file '%s': unexpected character at line %d (missing ']'?)",
218 file
.GetName(), n
+ 1);
219 continue; // skip this line
222 // group name here is always considered as abs path
225 strGroup
<< APPCONF_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
227 // will create it if doesn't yet exist
231 m_pCurrentGroup
->SetLine(m_linesTail
);
233 // check that there is nothing except comments left on this line
235 while ( *++pEnd
!= '\0' && bCont
) {
244 // ignore whitespace ('\n' impossible here)
248 wxLogWarning("file '%s', line %d: '%s' ignored after group header.",
249 file
.GetName(), n
+ 1, pEnd
);
255 const char *pEnd
= pStart
;
256 while ( IsValid(*pEnd
) )
259 wxString
strKey(pStart
, pEnd
);
262 while ( isspace(*pEnd
) )
265 if ( *pEnd
++ != '=' ) {
266 wxLogError("file '%s', line %d: '=' expected.", file
.GetName(), n
+ 1);
269 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
271 if ( pEntry
== NULL
) {
273 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
276 pEntry
->SetLine(m_linesTail
);
279 if ( bLocal
&& pEntry
->IsImmutable() ) {
280 // immutable keys can't be changed by user
281 wxLogWarning("file '%s', line %d: value for immutable key '%s' ignored.",
282 file
.GetName(), n
+ 1, strKey
.c_str());
285 // the condition below catches the cases (a) and (b) but not (c):
286 // (a) global key found second time in global file
287 // (b) key found second (or more) time in local file
288 // (c) key from global file now found in local one
289 // which is exactly what we want.
290 else if ( !bLocal
|| pEntry
->IsLocal() ) {
291 wxLogWarning("file '%s', line %d: key '%s' was first found at line %d.",
292 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
295 pEntry
->SetLine(m_linesTail
);
300 while ( isspace(*pEnd
) )
304 if (m_bExpandEnvVars
)
305 strValue
= ExpandEnvVars(FilterIn(pEnd
));
307 strValue
= FilterIn(pEnd
);
308 pEntry
->SetValue(strValue
, FALSE
);
314 // ----------------------------------------------------------------------------
316 // ----------------------------------------------------------------------------
318 void wxFileConfig::SetRootPath()
321 m_pCurrentGroup
= m_pRootGroup
;
324 void wxFileConfig::SetPath(const wxString
& strPath
)
326 wxArrayString aParts
;
328 if ( strPath
.IsEmpty() )
331 if ( strPath
[0] == APPCONF_PATH_SEPARATOR
) {
333 SplitPath(aParts
, strPath
);
336 // relative path, combine with current one
337 wxString strFullPath
= m_strPath
;
338 strFullPath
<< APPCONF_PATH_SEPARATOR
<< strPath
;
339 SplitPath(aParts
, strFullPath
);
342 // change current group
344 m_pCurrentGroup
= m_pRootGroup
;
345 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
346 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
347 if ( pNextGroup
== NULL
)
348 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
349 m_pCurrentGroup
= pNextGroup
;
352 // recombine path parts in one variable
354 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
355 m_strPath
<< APPCONF_PATH_SEPARATOR
<< aParts
[n
];
359 // ----------------------------------------------------------------------------
361 // ----------------------------------------------------------------------------
363 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
)
366 return GetNextGroup(str
, lIndex
);
369 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
)
371 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
372 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
379 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
)
382 return GetNextEntry(str
, lIndex
);
385 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
)
387 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
388 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
395 // ----------------------------------------------------------------------------
397 // ----------------------------------------------------------------------------
399 const char *wxFileConfig::Read(const char *szKey
,
400 const char *szDefault
) const
402 PathChanger
path(this, szKey
);
404 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
405 return pEntry
== NULL
? szDefault
: pEntry
->Value().c_str();
408 bool wxFileConfig::Read(wxString
*pstr
,
410 const char *szDefault
) const
412 PathChanger
path(this, szKey
);
414 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
415 if (pEntry
== NULL
) {
420 *pstr
= pEntry
->Value();
425 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
428 if ( Read(&str
, szKey
) ) {
438 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
440 PathChanger
path(this, szKey
);
442 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
443 if ( pEntry
== NULL
)
444 pEntry
= m_pCurrentGroup
->AddEntry(path
.Name());
445 pEntry
->SetValue(szValue
);
450 bool wxFileConfig::Write(const char *szKey
, long lValue
)
452 // ltoa() is not ANSI :-(
453 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
454 sprintf(szBuf
, "%ld", lValue
);
455 return Write(szKey
, szBuf
);
458 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
460 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
463 wxTempFile
file(m_strLocalFile
);
465 if ( !file
.IsOpened() ) {
466 wxLogError("Can't open user configuration file.");
470 // write all strings to file
471 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
472 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
473 wxLogError("Can't write user configuration file.");
478 return file
.Commit();
481 // ----------------------------------------------------------------------------
482 // delete groups/entries
483 // ----------------------------------------------------------------------------
485 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
487 PathChanger
path(this, szKey
);
489 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
492 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
493 if ( m_pCurrentGroup
!= m_pRootGroup
) {
494 ConfigGroup
*pGroup
= m_pCurrentGroup
;
495 SetPath(".."); // changes m_pCurrentGroup!
496 m_pCurrentGroup
->DeleteSubgroup(pGroup
->Name());
498 //else: never delete the root group
504 bool wxFileConfig::DeleteGroup(const char *szKey
)
506 PathChanger
path(this, szKey
);
508 return m_pCurrentGroup
->DeleteSubgroup(path
.Name());
511 bool wxFileConfig::DeleteAll()
513 const char *szFile
= m_strLocalFile
;
517 if ( remove(szFile
) == -1 )
518 wxLogSysError("Can't delete user configuration file '%s'", szFile
);
520 szFile
= m_strGlobalFile
;
521 if ( remove(szFile
) )
522 wxLogSysError("Can't delete system configuration file '%s'", szFile
);
527 // ----------------------------------------------------------------------------
528 // linked list functions
529 // ----------------------------------------------------------------------------
531 // append a new line to the end of the list
532 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
534 LineList
*pLine
= new LineList(str
);
536 if ( m_linesTail
== NULL
) {
542 m_linesTail
->SetNext(pLine
);
549 // insert a new line after the given one or in the very beginning if !pLine
550 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
553 if ( pLine
== m_linesTail
)
554 return LineListAppend(str
);
558 if ( pLine
== NULL
) {
559 pNewLine
= new LineList(str
, m_linesHead
);
560 m_linesHead
= pNewLine
;
563 pNewLine
= new LineList(str
, pLine
->Next());
564 pLine
->SetNext(pNewLine
);
570 bool wxFileConfig::LineListIsEmpty()
572 return m_linesHead
== NULL
;
575 // ============================================================================
576 // wxFileConfig::ConfigGroup
577 // ============================================================================
579 // ----------------------------------------------------------------------------
581 // ----------------------------------------------------------------------------
584 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
585 const wxString
& strName
,
586 wxFileConfig
*pConfig
)
597 // dtor deletes all children
598 wxFileConfig::ConfigGroup::~ConfigGroup()
601 uint n
, nCount
= m_aEntries
.Count();
602 for ( n
= 0; n
< nCount
; n
++ )
603 delete m_aEntries
[n
];
606 nCount
= m_aSubgroups
.Count();
607 for ( n
= 0; n
< nCount
; n
++ )
608 delete m_aSubgroups
[n
];
611 // ----------------------------------------------------------------------------
613 // ----------------------------------------------------------------------------
615 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
617 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
622 // return the line which contains "[our name]"
623 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
625 if ( m_pLine
== NULL
) {
626 // this group wasn't present in local config file, add it now
627 if ( Parent() != NULL
) {
628 wxString strFullName
;
629 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
630 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
631 Parent()->GetLastGroupLine());
632 Parent()->SetLastGroup(this);
635 // we return NULL, so that LineListInsert() will insert us in the
643 // return the last line belonging to the subgroups of this group
644 // (after which we can add a new subgroup)
645 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
647 // if we have any subgroups, our last line is the last line of the last
649 if ( m_pLastGroup
!= NULL
)
650 return m_pLastGroup
->GetLastGroupLine();
652 // if we have any entries, our last line is the last entry
653 if ( m_pLastEntry
!= NULL
)
654 return m_pLastEntry
->GetLine();
656 // nothing at all: last line is the first one
657 return GetGroupLine();
660 // return the last line belonging to the entries of this group
661 // (after which we can add a new entry)
662 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
664 if ( m_pLastEntry
!= NULL
) {
665 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
667 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
671 // no entrues: insert after the group header
672 return GetGroupLine();
675 // ----------------------------------------------------------------------------
677 // ----------------------------------------------------------------------------
679 wxString
wxFileConfig::ConfigGroup::GetFullName() const
682 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR
+ Name();
687 // ----------------------------------------------------------------------------
689 // ----------------------------------------------------------------------------
691 wxFileConfig::ConfigEntry
*
692 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
694 uint nCount
= m_aEntries
.Count();
695 for ( uint n
= 0; n
< nCount
; n
++ ) {
696 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
697 return m_aEntries
[n
];
703 wxFileConfig::ConfigGroup
*
704 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
706 uint nCount
= m_aSubgroups
.Count();
707 for ( uint n
= 0; n
< nCount
; n
++ ) {
708 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
709 return m_aSubgroups
[n
];
715 // ----------------------------------------------------------------------------
717 // ----------------------------------------------------------------------------
719 // create a new entry and add it to the current group
720 wxFileConfig::ConfigEntry
*
721 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
723 wxASSERT( FindEntry(strName
) == NULL
);
725 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
726 m_aEntries
.Add(pEntry
);
731 // create a new group and add it to the current group
732 wxFileConfig::ConfigGroup
*
733 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
735 wxASSERT( FindSubgroup(strName
) == NULL
);
737 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
738 m_aSubgroups
.Add(pGroup
);
743 // ----------------------------------------------------------------------------
745 // ----------------------------------------------------------------------------
747 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
749 uint n
, nCount
= m_aSubgroups
.Count();
750 for ( n
= 0; n
< nCount
; n
++ ) {
751 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
758 delete m_aSubgroups
[n
];
759 m_aSubgroups
.Remove(n
);
763 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
765 uint n
, nCount
= m_aEntries
.Count();
766 for ( n
= 0; n
< nCount
; n
++ ) {
767 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
774 delete m_aEntries
[n
];
775 m_aEntries
.Remove(n
);
779 // ----------------------------------------------------------------------------
781 // ----------------------------------------------------------------------------
782 void wxFileConfig::ConfigGroup::SetDirty()
785 if ( Parent() != NULL
) // propagate upwards
786 Parent()->SetDirty();
789 // ============================================================================
790 // wxFileConfig::ConfigEntry
791 // ============================================================================
793 // ----------------------------------------------------------------------------
795 // ----------------------------------------------------------------------------
796 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
797 const wxString
& strName
,
807 m_bImmutable
= strName
[0] == APPCONF_IMMUTABLE_PREFIX
;
809 m_strName
.erase(0, 1); // remove first character
812 // ----------------------------------------------------------------------------
814 // ----------------------------------------------------------------------------
816 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
818 if ( m_pLine
!= NULL
) {
819 wxLogWarning("Entry '%s' appears more than once in group '%s'",
820 Name().c_str(), m_pParent
->GetFullName().c_str());
824 Group()->SetLastEntry(this);
827 // second parameter is FALSE if we read the value from file and prevents the
828 // entry from being marked as 'dirty'
829 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
831 if ( bUser
&& IsImmutable() ) {
832 wxLogWarning("Attempt to change immutable key '%s' ignored.",
837 // do nothing if it's the same value
838 if ( strValue
== m_strValue
)
841 m_strValue
= strValue
;
844 wxString strVal
= FilterOut(strValue
);
846 strLine
<< m_strName
<< " = " << strVal
;
848 if ( m_pLine
!= NULL
) {
849 // entry was read from the local config file, just modify the line
850 m_pLine
->SetText(strLine
);
853 // add a new line to the file
854 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
856 m_pLine
= Group()->Config()->LineListInsert(strLine
,
857 Group()->GetLastEntryLine());
858 Group()->SetLastEntry(this);
865 void wxFileConfig::ConfigEntry::SetDirty()
871 // ============================================================================
873 // ============================================================================
876 wxString
FilterIn(const wxString
& str
)
880 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
882 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
883 if ( str
[n
] == '\\' ) {
884 switch ( str
[++n
] ) {
903 if ( str
[n
] != '"' || !bQuoted
)
905 else if ( n
!= str
.Len() - 1 )
906 wxLogWarning("unexpected \" at position %d in '%s'.", n
, str
.c_str());
907 //else: it's the last quote of a quoted string, ok
914 // quote the string before writing it to file
915 wxString
FilterOut(const wxString
& str
)
919 // quoting is necessary to preserve spaces in the beginning of the string
920 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
926 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
947 continue; // nothing special to do
950 // we get here only for special characters
951 strResult
<< '\\' << c
;