/////////////////////////////////////////////////////////////////////////////// // Name: fileconf.cpp // Purpose: implementation of wxFileConfig derivation of wxConfig // Author: Vadim Zeitlin // Modified by: // Created: 07.04.98 (adapted from appconf.cpp) // RCS-ID: $Id$ // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin // Ballueder@usa.net // Licence: wxWindows license /////////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif //__BORLANDC__ #ifndef WX_PRECOMP #include #include #endif //WX_PRECOMP #include #include #include #include #include #include // _WINDOWS_ is defined when windows.h is included, // __WINDOWS__ is defined for MS Windows compilation #if defined(__WINDOWS__) && !defined(_WINDOWS_) #include #endif //windows.h #include #include // ---------------------------------------------------------------------------- // global functions declarations // ---------------------------------------------------------------------------- // is 'c' a valid character in group name? // NB: APPCONF_IMMUTABLE_PREFIX and APPCONF_PATH_SEPARATOR must be valid chars, // but _not_ ']' (group name delimiter) inline bool IsValid(char c) { return isalnum(c) || strchr("_/-!.*%", c); } // filter strings static wxString FilterIn(const wxString& str); static wxString FilterOut(const wxString& str); // ============================================================================ // implementation // ============================================================================ // ---------------------------------------------------------------------------- // static functions // ---------------------------------------------------------------------------- wxString wxFileConfig::GetGlobalFileName(const char *szFile) { wxString str; bool bNoExt = strchr(szFile, '.') == NULL; #ifdef __UNIX__ str << "/etc/" << szFile; if ( bNoExt ) str << ".conf"; #else // Windows #ifndef _MAX_PATH #define _MAX_PATH 512 #endif char szWinDir[_MAX_PATH]; ::GetWindowsDirectory(szWinDir, _MAX_PATH); str << szWinDir << "\\" << szFile; if ( bNoExt ) str << ".ini"; #endif // UNIX/Win return str; } wxString wxFileConfig::GetLocalFileName(const char *szFile) { wxString str; #ifdef __UNIX__ const char *szHome = getenv("HOME"); if ( szHome == NULL ) { // we're homeless... wxLogWarning("can't find user's HOME, using current directory."); szHome = "."; } str << szHome << "/." << szFile; #else // Windows #ifdef __WIN32__ const char *szHome = getenv("HOMEDRIVE"); if ( szHome != NULL ) str << szHome; szHome = getenv("HOMEPATH"); if ( szHome != NULL ) str << szHome; str << szFile; if ( strchr(szFile, '.') == NULL ) str << ".ini"; #else // Win16 // Win16 has no idea about home, so use the current directory instead str << ".\\" << szFile; #endif // WIN16/32 #endif // UNIX/Win return str; } // ---------------------------------------------------------------------------- // ctor // ---------------------------------------------------------------------------- void wxFileConfig::Init() { m_pCurrentGroup = m_pRootGroup = new ConfigGroup(NULL, "", this); m_linesHead = m_linesTail = NULL; m_bExpandEnvVars = TRUE; m_strPath.Empty(); } wxFileConfig::wxFileConfig(const wxString& strLocal, const wxString& strGlobal) : m_strLocalFile(strLocal), m_strGlobalFile(strGlobal) { Init(); // it's not an error if (one of the) file(s) doesn't exist // parse the global file if ( !strGlobal.IsEmpty() ) { if ( wxFile::Exists(strGlobal) ) { wxTextFile fileGlobal(strGlobal); if ( fileGlobal.Open() ) { Parse(fileGlobal, FALSE /* global */); SetRootPath(); } else wxLogWarning("Can't open global configuration file '%s'.", strGlobal.c_str()); } } // parse the local file if ( wxFile::Exists(strLocal) ) { wxTextFile fileLocal(strLocal); if ( fileLocal.Open() ) { Parse(fileLocal, TRUE /* local */); SetRootPath(); } else wxLogWarning("Can't open user configuration file '%s'.", strLocal.c_str()); } } wxFileConfig::~wxFileConfig() { Flush(); delete m_pRootGroup; } // ---------------------------------------------------------------------------- // parse a config file // ---------------------------------------------------------------------------- void wxFileConfig::Parse(wxTextFile& file, bool bLocal) { const char *pStart; const char *pEnd; for ( uint n = 0; n < file.GetLineCount(); n++ ) { // add the line to linked list if ( bLocal ) LineListAppend(file[n]); // skip leading spaces for ( pStart = file[n]; isspace(*pStart); pStart++ ) ; // skip blank/comment lines if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' ) continue; if ( *pStart == '[' ) { // a new group pEnd = pStart; while ( *++pEnd != ']' ) { if ( !IsValid(*pEnd) && *pEnd != ' ' ) // allow spaces in group names break; } if ( *pEnd != ']' ) { wxLogError("file '%s': unexpected character at line %d (missing ']'?)", file.GetName(), n + 1); continue; // skip this line } // group name here is always considered as abs path wxString strGroup; pStart++; strGroup << APPCONF_PATH_SEPARATOR << wxString(pStart, pEnd - pStart); // will create it if doesn't yet exist SetPath(strGroup); if ( bLocal ) m_pCurrentGroup->SetLine(m_linesTail); // check that there is nothing except comments left on this line bool bCont = TRUE; while ( *++pEnd != '\0' && bCont ) { switch ( *pEnd ) { case '#': case ';': bCont = FALSE; break; case ' ': case '\t': // ignore whitespace ('\n' impossible here) break; default: wxLogWarning("file '%s', line %d: '%s' ignored after group header.", file.GetName(), n + 1, pEnd); bCont = FALSE; } } } else { // a key const char *pEnd = pStart; while ( IsValid(*pEnd) ) pEnd++; wxString strKey(pStart, pEnd); // skip whitespace while ( isspace(*pEnd) ) pEnd++; if ( *pEnd++ != '=' ) { wxLogError("file '%s', line %d: '=' expected.", file.GetName(), n + 1); } else { ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey); if ( pEntry == NULL ) { // new entry pEntry = m_pCurrentGroup->AddEntry(strKey, n); if ( bLocal ) pEntry->SetLine(m_linesTail); } else { if ( bLocal && pEntry->IsImmutable() ) { // immutable keys can't be changed by user wxLogWarning("file '%s', line %d: value for immutable key '%s' ignored.", file.GetName(), n + 1, strKey.c_str()); continue; } // the condition below catches the cases (a) and (b) but not (c): // (a) global key found second time in global file // (b) key found second (or more) time in local file // (c) key from global file now found in local one // which is exactly what we want. else if ( !bLocal || pEntry->IsLocal() ) { wxLogWarning("file '%s', line %d: key '%s' was first found at line %d.", file.GetName(), n + 1, strKey.c_str(), pEntry->Line()); if ( bLocal ) pEntry->SetLine(m_linesTail); } } // skip whitespace while ( isspace(*pEnd) ) pEnd++; wxString strValue; if (m_bExpandEnvVars) strValue = ExpandEnvVars(FilterIn(pEnd)); else strValue = FilterIn(pEnd); pEntry->SetValue(strValue, FALSE); } } } } // ---------------------------------------------------------------------------- // set/retrieve path // ---------------------------------------------------------------------------- void wxFileConfig::SetRootPath() { m_strPath.Empty(); m_pCurrentGroup = m_pRootGroup; } void wxFileConfig::SetPath(const wxString& strPath) { wxArrayString aParts; if ( strPath.IsEmpty() ) return; if ( strPath[0] == APPCONF_PATH_SEPARATOR ) { // absolute path SplitPath(aParts, strPath); } else { // relative path, combine with current one wxString strFullPath = m_strPath; strFullPath << APPCONF_PATH_SEPARATOR << strPath; SplitPath(aParts, strFullPath); } // change current group uint n; m_pCurrentGroup = m_pRootGroup; for ( n = 0; n < aParts.Count(); n++ ) { ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]); if ( pNextGroup == NULL ) pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]); m_pCurrentGroup = pNextGroup; } // recombine path parts in one variable m_strPath.Empty(); for ( n = 0; n < aParts.Count(); n++ ) { m_strPath << APPCONF_PATH_SEPARATOR << aParts[n]; } } // ---------------------------------------------------------------------------- // enumeration // ---------------------------------------------------------------------------- bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) { lIndex = 0; return GetNextGroup(str, lIndex); } bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) { if ( uint(lIndex) < m_pCurrentGroup->Groups().Count() ) { str = m_pCurrentGroup->Groups()[lIndex++]->Name(); return TRUE; } else return FALSE; } bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) { lIndex = 0; return GetNextEntry(str, lIndex); } bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) { if ( uint(lIndex) < m_pCurrentGroup->Entries().Count() ) { str = m_pCurrentGroup->Entries()[lIndex++]->Name(); return TRUE; } else return FALSE; } // ---------------------------------------------------------------------------- // read/write values // ---------------------------------------------------------------------------- const char *wxFileConfig::Read(const char *szKey, const char *szDefault) const { PathChanger path(this, szKey); ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name()); return pEntry == NULL ? szDefault : pEntry->Value().c_str(); } bool wxFileConfig::Read(wxString *pstr, const char *szKey, const char *szDefault) const { PathChanger path(this, szKey); ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name()); if (pEntry == NULL) { *pstr = szDefault; return FALSE; } else { *pstr = pEntry->Value(); return TRUE; } } bool wxFileConfig::Read(long *pl, const char *szKey, long lDefault) const { wxString str; if ( Read(&str, szKey) ) { *pl = atol(str); return TRUE; } else { *pl = lDefault; return FALSE; } } bool wxFileConfig::Write(const char *szKey, const char *szValue) { PathChanger path(this, szKey); ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name()); if ( pEntry == NULL ) pEntry = m_pCurrentGroup->AddEntry(path.Name()); pEntry->SetValue(szValue); return TRUE; } bool wxFileConfig::Write(const char *szKey, long lValue) { // ltoa() is not ANSI :-( char szBuf[40]; // should be good for sizeof(long) <= 16 (128 bits) sprintf(szBuf, "%ld", lValue); return Write(szKey, szBuf); } bool wxFileConfig::Flush(bool /* bCurrentOnly */) { if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() ) return TRUE; wxTempFile file(m_strLocalFile); if ( !file.IsOpened() ) { wxLogError("Can't open user configuration file."); return FALSE; } // write all strings to file for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) { if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) { wxLogError("Can't write user configuration file."); return FALSE; } } return file.Commit(); } // ---------------------------------------------------------------------------- // delete groups/entries // ---------------------------------------------------------------------------- bool wxFileConfig::DeleteEntry(const char *szKey, bool bGroupIfEmptyAlso) { PathChanger path(this, szKey); if ( !m_pCurrentGroup->DeleteEntry(path.Name()) ) return FALSE; if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) { if ( m_pCurrentGroup != m_pRootGroup ) { ConfigGroup *pGroup = m_pCurrentGroup; SetPath(".."); // changes m_pCurrentGroup! m_pCurrentGroup->DeleteSubgroup(pGroup->Name()); } //else: never delete the root group } return TRUE; } bool wxFileConfig::DeleteGroup(const char *szKey) { PathChanger path(this, szKey); return m_pCurrentGroup->DeleteSubgroup(path.Name()); } bool wxFileConfig::DeleteAll() { const char *szFile = m_strLocalFile; delete m_pRootGroup; Init(); if ( remove(szFile) == -1 ) wxLogSysError("Can't delete user configuration file '%s'", szFile); szFile = m_strGlobalFile; if ( remove(szFile) ) wxLogSysError("Can't delete system configuration file '%s'", szFile); return TRUE; } // ---------------------------------------------------------------------------- // linked list functions // ---------------------------------------------------------------------------- // append a new line to the end of the list wxFileConfig::LineList *wxFileConfig::LineListAppend(const wxString& str) { LineList *pLine = new LineList(str); if ( m_linesTail == NULL ) { // list is empty m_linesHead = pLine; } else { // adjust pointers m_linesTail->SetNext(pLine); } m_linesTail = pLine; return m_linesTail; } // insert a new line after the given one wxFileConfig::LineList *wxFileConfig::LineListInsert(const wxString& str, LineList *pLine) { if ( pLine == NULL ) return LineListAppend(str); LineList *pNewLine = new LineList(str, pLine->Next()); pLine->SetNext(pNewLine); return pNewLine; } bool wxFileConfig::LineListIsEmpty() { return m_linesHead == NULL; } // ============================================================================ // wxFileConfig::ConfigGroup // ============================================================================ // ---------------------------------------------------------------------------- // ctor/dtor // ---------------------------------------------------------------------------- // ctor wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup *pParent, const wxString& strName, wxFileConfig *pConfig) : m_strName(strName) { m_pConfig = pConfig; m_pParent = pParent; m_pLine = NULL; m_bDirty = FALSE; m_nLastEntry = m_nLastGroup = NOT_FOUND; } // dtor deletes all children wxFileConfig::ConfigGroup::~ConfigGroup() { // entries uint n, nCount = m_aEntries.Count(); for ( n = 0; n < nCount; n++ ) delete m_aEntries[n]; // subgroups nCount = m_aSubgroups.Count(); for ( n = 0; n < nCount; n++ ) delete m_aSubgroups[n]; } // ---------------------------------------------------------------------------- // line // ---------------------------------------------------------------------------- void wxFileConfig::ConfigGroup::SetLine(LineList *pLine) { wxASSERT( m_pLine == NULL ); // shouldn't be called twice m_pLine = pLine; } // return the line which contains "[our name]" wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetGroupLine() { if ( m_pLine == NULL ) { // this group wasn't present in local config file, add it now if ( Parent() != NULL ) { wxString strFullName; strFullName << "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/' m_pLine = m_pConfig->LineListInsert(strFullName, Parent()->GetLastGroupLine()); } else { // we're the root group, yet we were not in the local file => there were // only comments and blank lines in config file or nothing at all // we return NULL, so that LineListInsert() will do Append() } } return m_pLine; } // return the last line belonging to the subgroups of this group // (after which we can add a new subgroup) wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastGroupLine() { // if we have any subgroups, our last line is the last line of the last // subgroup if ( m_nLastGroup != NOT_FOUND ) { return m_aSubgroups[m_nLastGroup]->GetLastGroupLine(); } // if we have any entries, our last line is the last entry if ( m_nLastEntry != NOT_FOUND ) { return m_aEntries[m_nLastEntry]->GetLine(); } // nothing at all: last line is the first one return GetGroupLine(); } // return the last line belonging to the entries of this group // (after which we can add a new entry) wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastEntryLine() { if ( m_nLastEntry != NOT_FOUND ) return m_aEntries[m_nLastEntry]->GetLine(); else return GetGroupLine(); } // ---------------------------------------------------------------------------- // group name // ---------------------------------------------------------------------------- wxString wxFileConfig::ConfigGroup::GetFullName() const { if ( Parent() ) return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR + Name(); else return ""; } // ---------------------------------------------------------------------------- // find an item // ---------------------------------------------------------------------------- wxFileConfig::ConfigEntry * wxFileConfig::ConfigGroup::FindEntry(const char *szName) const { uint nCount = m_aEntries.Count(); for ( uint n = 0; n < nCount; n++ ) { if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) ) return m_aEntries[n]; } return NULL; } wxFileConfig::ConfigGroup * wxFileConfig::ConfigGroup::FindSubgroup(const char *szName) const { uint nCount = m_aSubgroups.Count(); for ( uint n = 0; n < nCount; n++ ) { if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) ) return m_aSubgroups[n]; } return NULL; } // ---------------------------------------------------------------------------- // create a new item // ---------------------------------------------------------------------------- // create a new entry and add it to the current group wxFileConfig::ConfigEntry * wxFileConfig::ConfigGroup::AddEntry(const wxString& strName, int nLine) { wxASSERT( FindEntry(strName) == NULL ); ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine); m_aEntries.Add(pEntry); return pEntry; } // create a new group and add it to the current group wxFileConfig::ConfigGroup * wxFileConfig::ConfigGroup::AddSubgroup(const wxString& strName) { wxASSERT( FindSubgroup(strName) == NULL ); ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig); m_aSubgroups.Add(pGroup); return pGroup; } // ---------------------------------------------------------------------------- // delete an item // ---------------------------------------------------------------------------- bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName) { uint n, nCount = m_aSubgroups.Count(); for ( n = 0; n < nCount; n++ ) { if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) ) break; } if ( n == nCount ) return FALSE; delete m_aSubgroups[n]; m_aSubgroups.Remove(n); return TRUE; } bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName) { uint n, nCount = m_aEntries.Count(); for ( n = 0; n < nCount; n++ ) { if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) ) break; } if ( n == nCount ) return FALSE; delete m_aEntries[n]; m_aEntries.Remove(n); return TRUE; } // ---------------------------------------------------------------------------- // // ---------------------------------------------------------------------------- void wxFileConfig::ConfigGroup::SetDirty() { m_bDirty = TRUE; if ( Parent() != NULL ) // propagate upwards Parent()->SetDirty(); } // ============================================================================ // wxFileConfig::ConfigEntry // ============================================================================ // ---------------------------------------------------------------------------- // ctor // ---------------------------------------------------------------------------- wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent, const wxString& strName, int nLine) : m_strName(strName) { m_pParent = pParent; m_nLine = nLine; m_pLine = NULL; m_bDirty = FALSE; m_bImmutable = strName[0] == APPCONF_IMMUTABLE_PREFIX; if ( m_bImmutable ) m_strName.erase(0, 1); // remove first character } // ---------------------------------------------------------------------------- // set value // ---------------------------------------------------------------------------- void wxFileConfig::ConfigEntry::SetLine(LineList *pLine) { if ( m_pLine != NULL ) { wxLogWarning("Entry '%s' appears more than once in group '%s'", Name().c_str(), m_pParent->GetFullName().c_str()); } m_pLine = pLine; } // second parameter is FALSE if we read the value from file and prevents the // entry from being marked as 'dirty' void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser) { if ( bUser && IsImmutable() ) { wxLogWarning("Attempt to change immutable key '%s' ignored.", Name().c_str()); return; } // do nothing if it's the same value if ( strValue == m_strValue ) return; m_strValue = strValue; if ( bUser ) { wxString strVal = FilterOut(strValue); wxString strLine; strLine << m_strName << " = " << strVal; if ( m_pLine != NULL ) { // entry was read from the local config file, just modify the line m_pLine->SetText(strLine); } else { // add a new line to the file wxASSERT( m_nLine == NOT_FOUND ); // consistency check Group()->Config()->LineListInsert(strLine, Group()->GetLastEntryLine()); } SetDirty(); } } void wxFileConfig::ConfigEntry::SetDirty() { m_bDirty = TRUE; Group()->SetDirty(); } // ============================================================================ // global functions // ============================================================================ // undo FilterOut wxString FilterIn(const wxString& str) { wxString strResult; bool bQuoted = !str.IsEmpty() && str[0] == '"'; for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) { if ( str[n] == '\\' ) { switch ( str[++n] ) { case 'n': strResult += '\n'; break; case 't': strResult += '\t'; break; case '\\': strResult += '\\'; break; case '"': strResult += '"'; break; } } else { if ( str[n] != '"' || !bQuoted ) strResult += str[n]; else if ( n != str.Len() - 1 ) wxLogWarning("unexpected \" at position %d in '%s'.", n, str.c_str()); //else: it's the last quote of a quoted string, ok } } return strResult; } // quote the string before writing it to file wxString FilterOut(const wxString& str) { wxString strResult; // quoting is necessary to preserve spaces in the beginning of the string bool bQuote = isspace(str[0]) || str[0] == '"'; if ( bQuote ) strResult += '"'; char c; for ( uint n = 0; n < str.Len(); n++ ) { switch ( str[n] ) { case '\n': c = 'n'; break; case '\t': c = 't'; break; case '\\': c = '\\'; break; case '"': if ( bQuote ) c = '"'; //else: fall through default: strResult += str[n]; continue; // nothing special to do } // we get here only for special characters strResult << '\\' << c; } if ( bQuote ) strResult += '"'; return strResult; }