]>
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 ///////////////////////////////////////////////////////////////////////////////
13 // ============================================================================
15 // ============================================================================
17 // ----------------------------------------------------------------------------
19 // ----------------------------------------------------------------------------
20 #include "wx/wxprec.h"
27 #include <wx/string.h>
31 #include <wx/dynarray.h>
34 #include <wx/textfile.h>
35 #include <wx/config.h>
36 #include <wx/fileconf.h>
38 // _WINDOWS_ is defined when windows.h is included,
39 // __WINDOWS__ is defined for MS Windows compilation
40 #if defined(__WINDOWS__) && !defined(_WINDOWS_)
47 // ----------------------------------------------------------------------------
48 // global functions declarations
49 // ----------------------------------------------------------------------------
51 // is 'c' a valid character in group name?
52 // NB: APPCONF_IMMUTABLE_PREFIX and APPCONF_PATH_SEPARATOR must be valid chars,
53 // but _not_ ']' (group name delimiter)
54 inline bool IsValid(char c
) { return isalnum(c
) || strchr("_/-!.*%", c
); }
57 static wxString
FilterIn(const wxString
& str
);
58 static wxString
FilterOut(const wxString
& str
);
60 // ============================================================================
62 // ============================================================================
64 // ----------------------------------------------------------------------------
66 // ----------------------------------------------------------------------------
67 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
71 bool bNoExt
= strchr(szFile
, '.') == NULL
;
74 str
<< "/etc/" << szFile
;
82 char szWinDir
[_MAX_PATH
];
83 ::GetWindowsDirectory(szWinDir
, _MAX_PATH
);
84 str
<< szWinDir
<< "\\" << szFile
;
92 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
97 const char *szHome
= getenv("HOME");
98 if ( szHome
== NULL
) {
100 wxLogWarning("can't find user's HOME, using current directory.");
103 str
<< szHome
<< "/." << szFile
;
106 const char *szHome
= getenv("HOMEDRIVE");
107 if ( szHome
!= NULL
)
109 szHome
= getenv("HOMEPATH");
110 if ( szHome
!= NULL
)
113 if ( strchr(szFile
, '.') == NULL
)
116 // Win16 has no idea about home, so use the current directory instead
117 str
<< ".\\" << szFile
;
124 // ----------------------------------------------------------------------------
126 // ----------------------------------------------------------------------------
128 void wxFileConfig::Init()
131 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
136 m_bExpandEnvVars
= TRUE
;
141 wxFileConfig::wxFileConfig(const wxString
& strLocal
, const wxString
& strGlobal
)
142 : m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
146 // it's not an error if (one of the) file(s) doesn't exist
148 // parse the global file
149 if ( !strGlobal
.IsEmpty() ) {
150 if ( wxFile::Exists(strGlobal
) ) {
151 wxTextFile
fileGlobal(strGlobal
);
153 if ( fileGlobal
.Open() ) {
154 Parse(fileGlobal
, FALSE
/* global */);
158 wxLogWarning("Can't open global configuration file '%s'.",
163 // parse the local file
164 if ( wxFile::Exists(strLocal
) ) {
165 wxTextFile
fileLocal(strLocal
);
166 if ( fileLocal
.Open() ) {
167 Parse(fileLocal
, TRUE
/* local */);
171 wxLogWarning("Can't open user configuration file '%s'.",
176 wxFileConfig::~wxFileConfig()
182 // ----------------------------------------------------------------------------
183 // parse a config file
184 // ----------------------------------------------------------------------------
186 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
191 for ( uint n
= 0; n
< file
.GetLineCount(); n
++ ) {
192 // add the line to linked list
194 LineListAppend(file
[n
]);
196 // skip leading spaces
197 for ( pStart
= file
[n
]; isspace(*pStart
); pStart
++ )
200 // skip blank/comment lines
201 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
204 if ( *pStart
== '[' ) { // a new group
207 while ( *++pEnd
!= ']' ) {
208 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
212 if ( *pEnd
!= ']' ) {
213 wxLogError("file '%s': unexpected character at line %d (missing ']'?)",
214 file
.GetName(), n
+ 1);
215 continue; // skip this line
218 // group name here is always considered as abs path
221 strGroup
<< APPCONF_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
223 // will create it if doesn't yet exist
227 m_pCurrentGroup
->SetLine(m_linesTail
);
229 // check that there is nothing except comments left on this line
231 while ( *++pEnd
!= '\0' && bCont
) {
240 // ignore whitespace ('\n' impossible here)
244 wxLogWarning("file '%s', line %d: '%s' ignored after group header.",
245 file
.GetName(), n
+ 1, pEnd
);
251 const char *pEnd
= pStart
;
252 while ( IsValid(*pEnd
) )
255 wxString
strKey(pStart
, pEnd
);
258 while ( isspace(*pEnd
) )
261 if ( *pEnd
++ != '=' ) {
262 wxLogError("file '%s', line %d: '=' expected.", file
.GetName(), n
+ 1);
265 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
267 if ( pEntry
== NULL
) {
269 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
272 pEntry
->SetLine(m_linesTail
);
275 if ( bLocal
&& pEntry
->IsImmutable() ) {
276 // immutable keys can't be changed by user
277 wxLogWarning("file '%s', line %d: value for immutable key '%s' ignored.",
278 file
.GetName(), n
+ 1, strKey
.c_str());
281 // the condition below catches the cases (a) and (b) but not (c):
282 // (a) global key found second time in global file
283 // (b) key found second (or more) time in local file
284 // (c) key from global file now found in local one
285 // which is exactly what we want.
286 else if ( !bLocal
|| pEntry
->IsLocal() ) {
287 wxLogWarning("file '%s', line %d: key '%s' was first found at line %d.",
288 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
291 pEntry
->SetLine(m_linesTail
);
296 while ( isspace(*pEnd
) )
300 if (m_bExpandEnvVars
)
301 strValue
= ExpandEnvVars(FilterIn(pEnd
));
303 strValue
= FilterIn(pEnd
);
304 pEntry
->SetValue(strValue
, FALSE
);
310 // ----------------------------------------------------------------------------
312 // ----------------------------------------------------------------------------
314 void wxFileConfig::SetRootPath()
317 m_pCurrentGroup
= m_pRootGroup
;
320 void wxFileConfig::SetPath(const wxString
& strPath
)
322 wxArrayString aParts
;
324 if ( strPath
.IsEmpty() )
327 if ( strPath
[0] == APPCONF_PATH_SEPARATOR
) {
329 SplitPath(aParts
, strPath
);
332 // relative path, combine with current one
333 wxString strFullPath
= m_strPath
;
334 strFullPath
<< APPCONF_PATH_SEPARATOR
<< strPath
;
335 SplitPath(aParts
, strFullPath
);
338 // change current group
340 m_pCurrentGroup
= m_pRootGroup
;
341 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
342 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
343 if ( pNextGroup
== NULL
)
344 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
345 m_pCurrentGroup
= pNextGroup
;
348 // recombine path parts in one variable
350 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
351 m_strPath
<< APPCONF_PATH_SEPARATOR
<< aParts
[n
];
355 // ----------------------------------------------------------------------------
357 // ----------------------------------------------------------------------------
359 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
)
362 return GetNextGroup(str
, lIndex
);
365 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
)
367 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
368 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
375 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
)
378 return GetNextEntry(str
, lIndex
);
381 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
)
383 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
384 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
391 // ----------------------------------------------------------------------------
393 // ----------------------------------------------------------------------------
395 const char *wxFileConfig::Read(const char *szKey
,
396 const char *szDefault
) const
398 PathChanger
path(this, szKey
);
400 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
401 return pEntry
== NULL
? szDefault
: pEntry
->Value().c_str();
404 bool wxFileConfig::Read(wxString
*pstr
,
406 const char *szDefault
) const
408 PathChanger
path(this, szKey
);
410 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
411 if (pEntry
== NULL
) {
416 *pstr
= pEntry
->Value();
421 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
424 if ( Read(&str
, szKey
) ) {
434 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
436 PathChanger
path(this, szKey
);
438 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
439 if ( pEntry
== NULL
)
440 pEntry
= m_pCurrentGroup
->AddEntry(path
.Name());
441 pEntry
->SetValue(szValue
);
446 bool wxFileConfig::Write(const char *szKey
, long lValue
)
448 // ltoa() is not ANSI :-(
449 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
450 sprintf(szBuf
, "%ld", lValue
);
451 return Write(szKey
, szBuf
);
454 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
456 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
459 wxTempFile
file(m_strLocalFile
);
461 if ( !file
.IsOpened() ) {
462 wxLogError("Can't open user configuration file.");
466 // write all strings to file
467 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
468 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
469 wxLogError("Can't write user configuration file.");
474 return file
.Commit();
477 // ----------------------------------------------------------------------------
478 // delete groups/entries
479 // ----------------------------------------------------------------------------
481 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
483 PathChanger
path(this, szKey
);
485 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
488 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
489 if ( m_pCurrentGroup
!= m_pRootGroup
) {
490 ConfigGroup
*pGroup
= m_pCurrentGroup
;
491 SetPath(".."); // changes m_pCurrentGroup!
492 m_pCurrentGroup
->DeleteSubgroup(pGroup
->Name());
494 //else: never delete the root group
500 bool wxFileConfig::DeleteGroup(const char *szKey
)
502 PathChanger
path(this, szKey
);
504 return m_pCurrentGroup
->DeleteSubgroup(path
.Name());
507 bool wxFileConfig::DeleteAll()
509 const char *szFile
= m_strLocalFile
;
513 if ( remove(szFile
) == -1 )
514 wxLogSysError("Can't delete user configuration file '%s'", szFile
);
516 szFile
= m_strGlobalFile
;
517 if ( remove(szFile
) )
518 wxLogSysError("Can't delete system configuration file '%s'", szFile
);
523 // ----------------------------------------------------------------------------
524 // linked list functions
525 // ----------------------------------------------------------------------------
527 // append a new line to the end of the list
528 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
530 LineList
*pLine
= new LineList(str
);
532 if ( m_linesTail
== NULL
) {
538 m_linesTail
->SetNext(pLine
);
545 // insert a new line after the given one or in the very beginning if !pLine
546 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
549 if ( pLine
== m_linesTail
)
550 return LineListAppend(str
);
554 if ( pLine
== NULL
) {
555 pNewLine
= new LineList(str
, m_linesHead
);
556 m_linesHead
= pNewLine
;
559 pNewLine
= new LineList(str
, pLine
->Next());
560 pLine
->SetNext(pNewLine
);
566 bool wxFileConfig::LineListIsEmpty()
568 return m_linesHead
== NULL
;
571 // ============================================================================
572 // wxFileConfig::ConfigGroup
573 // ============================================================================
575 // ----------------------------------------------------------------------------
577 // ----------------------------------------------------------------------------
580 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
581 const wxString
& strName
,
582 wxFileConfig
*pConfig
)
593 // dtor deletes all children
594 wxFileConfig::ConfigGroup::~ConfigGroup()
597 uint n
, nCount
= m_aEntries
.Count();
598 for ( n
= 0; n
< nCount
; n
++ )
599 delete m_aEntries
[n
];
602 nCount
= m_aSubgroups
.Count();
603 for ( n
= 0; n
< nCount
; n
++ )
604 delete m_aSubgroups
[n
];
607 // ----------------------------------------------------------------------------
609 // ----------------------------------------------------------------------------
611 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
613 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
618 // return the line which contains "[our name]"
619 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
621 if ( m_pLine
== NULL
) {
622 // this group wasn't present in local config file, add it now
623 if ( Parent() != NULL
) {
624 wxString strFullName
;
625 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
626 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
627 Parent()->GetLastGroupLine());
628 Parent()->SetLastGroup(this);
631 // we return NULL, so that LineListInsert() will insert us in the
639 // return the last line belonging to the subgroups of this group
640 // (after which we can add a new subgroup)
641 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
643 // if we have any subgroups, our last line is the last line of the last
645 if ( m_pLastGroup
!= NULL
)
646 return m_pLastGroup
->GetLastGroupLine();
648 // if we have any entries, our last line is the last entry
649 if ( m_pLastEntry
!= NULL
)
650 return m_pLastEntry
->GetLine();
652 // nothing at all: last line is the first one
653 return GetGroupLine();
656 // return the last line belonging to the entries of this group
657 // (after which we can add a new entry)
658 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
660 if ( m_pLastEntry
!= NULL
) {
661 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
663 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
667 // no entrues: insert after the group header
668 return GetGroupLine();
671 // ----------------------------------------------------------------------------
673 // ----------------------------------------------------------------------------
675 wxString
wxFileConfig::ConfigGroup::GetFullName() const
678 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR
+ Name();
683 // ----------------------------------------------------------------------------
685 // ----------------------------------------------------------------------------
687 wxFileConfig::ConfigEntry
*
688 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
690 uint nCount
= m_aEntries
.Count();
691 for ( uint n
= 0; n
< nCount
; n
++ ) {
692 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
693 return m_aEntries
[n
];
699 wxFileConfig::ConfigGroup
*
700 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
702 uint nCount
= m_aSubgroups
.Count();
703 for ( uint n
= 0; n
< nCount
; n
++ ) {
704 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
705 return m_aSubgroups
[n
];
711 // ----------------------------------------------------------------------------
713 // ----------------------------------------------------------------------------
715 // create a new entry and add it to the current group
716 wxFileConfig::ConfigEntry
*
717 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
719 wxASSERT( FindEntry(strName
) == NULL
);
721 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
722 m_aEntries
.Add(pEntry
);
727 // create a new group and add it to the current group
728 wxFileConfig::ConfigGroup
*
729 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
731 wxASSERT( FindSubgroup(strName
) == NULL
);
733 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
734 m_aSubgroups
.Add(pGroup
);
739 // ----------------------------------------------------------------------------
741 // ----------------------------------------------------------------------------
743 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
745 uint n
, nCount
= m_aSubgroups
.Count();
746 for ( n
= 0; n
< nCount
; n
++ ) {
747 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
754 delete m_aSubgroups
[n
];
755 m_aSubgroups
.Remove(n
);
759 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
761 uint n
, nCount
= m_aEntries
.Count();
762 for ( n
= 0; n
< nCount
; n
++ ) {
763 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
770 delete m_aEntries
[n
];
771 m_aEntries
.Remove(n
);
775 // ----------------------------------------------------------------------------
777 // ----------------------------------------------------------------------------
778 void wxFileConfig::ConfigGroup::SetDirty()
781 if ( Parent() != NULL
) // propagate upwards
782 Parent()->SetDirty();
785 // ============================================================================
786 // wxFileConfig::ConfigEntry
787 // ============================================================================
789 // ----------------------------------------------------------------------------
791 // ----------------------------------------------------------------------------
792 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
793 const wxString
& strName
,
803 m_bImmutable
= strName
[0] == APPCONF_IMMUTABLE_PREFIX
;
805 m_strName
.erase(0, 1); // remove first character
808 // ----------------------------------------------------------------------------
810 // ----------------------------------------------------------------------------
812 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
814 if ( m_pLine
!= NULL
) {
815 wxLogWarning("Entry '%s' appears more than once in group '%s'",
816 Name().c_str(), m_pParent
->GetFullName().c_str());
820 Group()->SetLastEntry(this);
823 // second parameter is FALSE if we read the value from file and prevents the
824 // entry from being marked as 'dirty'
825 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
827 if ( bUser
&& IsImmutable() ) {
828 wxLogWarning("Attempt to change immutable key '%s' ignored.",
833 // do nothing if it's the same value
834 if ( strValue
== m_strValue
)
837 m_strValue
= strValue
;
840 wxString strVal
= FilterOut(strValue
);
842 strLine
<< m_strName
<< " = " << strVal
;
844 if ( m_pLine
!= NULL
) {
845 // entry was read from the local config file, just modify the line
846 m_pLine
->SetText(strLine
);
849 // add a new line to the file
850 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
852 m_pLine
= Group()->Config()->LineListInsert(strLine
,
853 Group()->GetLastEntryLine());
854 Group()->SetLastEntry(this);
861 void wxFileConfig::ConfigEntry::SetDirty()
867 // ============================================================================
869 // ============================================================================
872 wxString
FilterIn(const wxString
& str
)
876 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
878 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
879 if ( str
[n
] == '\\' ) {
880 switch ( str
[++n
] ) {
899 if ( str
[n
] != '"' || !bQuoted
)
901 else if ( n
!= str
.Len() - 1 )
902 wxLogWarning("unexpected \" at position %d in '%s'.", n
, str
.c_str());
903 //else: it's the last quote of a quoted string, ok
910 // quote the string before writing it to file
911 wxString
FilterOut(const wxString
& str
)
915 // quoting is necessary to preserve spaces in the beginning of the string
916 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
922 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
943 continue; // nothing special to do
946 // we get here only for special characters
947 strResult
<< '\\' << c
;