]>
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
546 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
550 return LineListAppend(str
);
552 LineList
*pNewLine
= new LineList(str
, pLine
->Next());
553 pLine
->SetNext(pNewLine
);
558 bool wxFileConfig::LineListIsEmpty()
560 return m_linesHead
== NULL
;
563 // ============================================================================
564 // wxFileConfig::ConfigGroup
565 // ============================================================================
567 // ----------------------------------------------------------------------------
569 // ----------------------------------------------------------------------------
572 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
573 const wxString
& strName
,
574 wxFileConfig
*pConfig
)
583 m_nLastGroup
= NOT_FOUND
;
586 // dtor deletes all children
587 wxFileConfig::ConfigGroup::~ConfigGroup()
590 uint n
, nCount
= m_aEntries
.Count();
591 for ( n
= 0; n
< nCount
; n
++ )
592 delete m_aEntries
[n
];
595 nCount
= m_aSubgroups
.Count();
596 for ( n
= 0; n
< nCount
; n
++ )
597 delete m_aSubgroups
[n
];
600 // ----------------------------------------------------------------------------
602 // ----------------------------------------------------------------------------
604 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
606 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
611 // return the line which contains "[our name]"
612 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
614 if ( m_pLine
== NULL
) {
615 // this group wasn't present in local config file, add it now
616 if ( Parent() != NULL
) {
617 wxString strFullName
;
618 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
619 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
620 Parent()->GetLastGroupLine());
623 // we're the root group, yet we were not in the local file => there were
624 // only comments and blank lines in config file or nothing at all
625 // we return NULL, so that LineListInsert() will do Append()
632 // return the last line belonging to the subgroups of this group
633 // (after which we can add a new subgroup)
634 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
636 // if we have any subgroups, our last line is the last line of the last
638 if ( m_nLastGroup
!= NOT_FOUND
) {
639 return m_aSubgroups
[m_nLastGroup
]->GetLastGroupLine();
642 // if we have any entries, our last line is the last entry
643 if ( m_nLastEntry
!= NOT_FOUND
) {
644 return m_aEntries
[m_nLastEntry
]->GetLine();
647 // nothing at all: last line is the first one
648 return GetGroupLine();
651 // return the last line belonging to the entries of this group
652 // (after which we can add a new entry)
653 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
655 if ( m_nLastEntry
!= NOT_FOUND
)
656 return m_aEntries
[m_nLastEntry
]->GetLine();
658 return GetGroupLine();
661 // ----------------------------------------------------------------------------
663 // ----------------------------------------------------------------------------
665 wxString
wxFileConfig::ConfigGroup::GetFullName() const
668 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR
+ Name();
673 // ----------------------------------------------------------------------------
675 // ----------------------------------------------------------------------------
677 wxFileConfig::ConfigEntry
*
678 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
680 uint nCount
= m_aEntries
.Count();
681 for ( uint n
= 0; n
< nCount
; n
++ ) {
682 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
683 return m_aEntries
[n
];
689 wxFileConfig::ConfigGroup
*
690 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
692 uint nCount
= m_aSubgroups
.Count();
693 for ( uint n
= 0; n
< nCount
; n
++ ) {
694 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
695 return m_aSubgroups
[n
];
701 // ----------------------------------------------------------------------------
703 // ----------------------------------------------------------------------------
705 // create a new entry and add it to the current group
706 wxFileConfig::ConfigEntry
*
707 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
709 wxASSERT( FindEntry(strName
) == NULL
);
711 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
712 m_aEntries
.Add(pEntry
);
717 // create a new group and add it to the current group
718 wxFileConfig::ConfigGroup
*
719 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
721 wxASSERT( FindSubgroup(strName
) == NULL
);
723 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
724 m_aSubgroups
.Add(pGroup
);
729 // ----------------------------------------------------------------------------
731 // ----------------------------------------------------------------------------
733 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
735 uint n
, nCount
= m_aSubgroups
.Count();
736 for ( n
= 0; n
< nCount
; n
++ ) {
737 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
744 delete m_aSubgroups
[n
];
745 m_aSubgroups
.Remove(n
);
749 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
751 uint n
, nCount
= m_aEntries
.Count();
752 for ( n
= 0; n
< nCount
; n
++ ) {
753 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
760 delete m_aEntries
[n
];
761 m_aEntries
.Remove(n
);
765 // ----------------------------------------------------------------------------
767 // ----------------------------------------------------------------------------
768 void wxFileConfig::ConfigGroup::SetDirty()
771 if ( Parent() != NULL
) // propagate upwards
772 Parent()->SetDirty();
775 // ============================================================================
776 // wxFileConfig::ConfigEntry
777 // ============================================================================
779 // ----------------------------------------------------------------------------
781 // ----------------------------------------------------------------------------
782 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
783 const wxString
& strName
,
793 m_bImmutable
= strName
[0] == APPCONF_IMMUTABLE_PREFIX
;
795 m_strName
.erase(0, 1); // remove first character
798 // ----------------------------------------------------------------------------
800 // ----------------------------------------------------------------------------
802 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
804 if ( m_pLine
!= NULL
) {
805 wxLogWarning("Entry '%s' appears more than once in group '%s'",
806 Name().c_str(), m_pParent
->GetFullName().c_str());
812 // second parameter is FALSE if we read the value from file and prevents the
813 // entry from being marked as 'dirty'
814 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
816 if ( bUser
&& IsImmutable() ) {
817 wxLogWarning("Attempt to change immutable key '%s' ignored.",
822 // do nothing if it's the same value
823 if ( strValue
== m_strValue
)
826 m_strValue
= strValue
;
829 wxString strVal
= FilterOut(strValue
);
831 strLine
<< m_strName
<< " = " << strVal
;
833 if ( m_pLine
!= NULL
) {
834 // entry was read from the local config file, just modify the line
835 m_pLine
->SetText(strLine
);
838 // add a new line to the file
839 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
841 Group()->Config()->LineListInsert(strLine
, Group()->GetLastEntryLine());
848 void wxFileConfig::ConfigEntry::SetDirty()
854 // ============================================================================
856 // ============================================================================
859 wxString
FilterIn(const wxString
& str
)
863 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
865 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
866 if ( str
[n
] == '\\' ) {
867 switch ( str
[++n
] ) {
886 if ( str
[n
] != '"' || !bQuoted
)
888 else if ( n
!= str
.Len() - 1 )
889 wxLogWarning("unexpected \" at position %d in '%s'.", n
, str
.c_str());
890 //else: it's the last quote of a quoted string, ok
897 // quote the string before writing it to file
898 wxString
FilterOut(const wxString
& str
)
902 // quoting is necessary to preserve spaces in the beginning of the string
903 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
909 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
930 continue; // nothing special to do
933 // we get here only for special characters
934 strResult
<< '\\' << c
;