]>
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 // __WXMSW__ is defined for MS Windows compilation
44 #if defined(__WXMSW__) && !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()
185 LineList
*pCur
= m_linesHead
;
186 while ( pCur
!= NULL
) {
187 LineList
*pNext
= pCur
->Next();
193 // ----------------------------------------------------------------------------
194 // parse a config file
195 // ----------------------------------------------------------------------------
197 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
202 for ( uint n
= 0; n
< file
.GetLineCount(); n
++ ) {
203 // add the line to linked list
205 LineListAppend(file
[n
]);
207 // skip leading spaces
208 for ( pStart
= file
[n
]; isspace(*pStart
); pStart
++ )
211 // skip blank/comment lines
212 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
215 if ( *pStart
== '[' ) { // a new group
218 while ( *++pEnd
!= ']' ) {
219 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
223 if ( *pEnd
!= ']' ) {
224 wxLogError("file '%s': unexpected character at line %d (missing ']'?)",
225 file
.GetName(), n
+ 1);
226 continue; // skip this line
229 // group name here is always considered as abs path
232 strGroup
<< APPCONF_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
234 // will create it if doesn't yet exist
238 m_pCurrentGroup
->SetLine(m_linesTail
);
240 // check that there is nothing except comments left on this line
242 while ( *++pEnd
!= '\0' && bCont
) {
251 // ignore whitespace ('\n' impossible here)
255 wxLogWarning("file '%s', line %d: '%s' ignored after group header.",
256 file
.GetName(), n
+ 1, pEnd
);
262 const char *pEnd
= pStart
;
263 while ( IsValid(*pEnd
) )
266 wxString
strKey(pStart
, pEnd
);
269 while ( isspace(*pEnd
) )
272 if ( *pEnd
++ != '=' ) {
273 wxLogError("file '%s', line %d: '=' expected.", file
.GetName(), n
+ 1);
276 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
278 if ( pEntry
== NULL
) {
280 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
283 pEntry
->SetLine(m_linesTail
);
286 if ( bLocal
&& pEntry
->IsImmutable() ) {
287 // immutable keys can't be changed by user
288 wxLogWarning("file '%s', line %d: value for immutable key '%s' ignored.",
289 file
.GetName(), n
+ 1, strKey
.c_str());
292 // the condition below catches the cases (a) and (b) but not (c):
293 // (a) global key found second time in global file
294 // (b) key found second (or more) time in local file
295 // (c) key from global file now found in local one
296 // which is exactly what we want.
297 else if ( !bLocal
|| pEntry
->IsLocal() ) {
298 wxLogWarning("file '%s', line %d: key '%s' was first found at line %d.",
299 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
302 pEntry
->SetLine(m_linesTail
);
307 while ( isspace(*pEnd
) )
311 if (m_bExpandEnvVars
)
312 strValue
= ExpandEnvVars(FilterIn(pEnd
));
314 strValue
= FilterIn(pEnd
);
315 pEntry
->SetValue(strValue
, FALSE
);
321 // ----------------------------------------------------------------------------
323 // ----------------------------------------------------------------------------
325 void wxFileConfig::SetRootPath()
328 m_pCurrentGroup
= m_pRootGroup
;
331 void wxFileConfig::SetPath(const wxString
& strPath
)
333 wxArrayString aParts
;
335 if ( strPath
.IsEmpty() ) {
340 if ( strPath
[0] == APPCONF_PATH_SEPARATOR
) {
342 SplitPath(aParts
, strPath
);
345 // relative path, combine with current one
346 wxString strFullPath
= m_strPath
;
347 strFullPath
<< APPCONF_PATH_SEPARATOR
<< strPath
;
348 SplitPath(aParts
, strFullPath
);
351 // change current group
353 m_pCurrentGroup
= m_pRootGroup
;
354 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
355 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
356 if ( pNextGroup
== NULL
)
357 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
358 m_pCurrentGroup
= pNextGroup
;
361 // recombine path parts in one variable
363 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
364 m_strPath
<< APPCONF_PATH_SEPARATOR
<< aParts
[n
];
368 // ----------------------------------------------------------------------------
370 // ----------------------------------------------------------------------------
372 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
)
375 return GetNextGroup(str
, lIndex
);
378 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
)
380 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
381 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
388 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
)
391 return GetNextEntry(str
, lIndex
);
394 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
)
396 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
397 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
404 // ----------------------------------------------------------------------------
405 // tests for existence
406 // ----------------------------------------------------------------------------
408 bool wxFileConfig::HasGroup(const wxString
& strName
) const
410 PathChanger
path(this, strName
);
412 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
413 return pGroup
!= NULL
;
416 bool wxFileConfig::HasEntry(const wxString
& strName
) const
418 PathChanger
path(this, strName
);
420 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
421 return pEntry
!= NULL
;
424 // ----------------------------------------------------------------------------
426 // ----------------------------------------------------------------------------
428 const char *wxFileConfig::Read(const char *szKey
,
429 const char *szDefault
) const
431 PathChanger
path(this, szKey
);
433 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
434 return pEntry
== NULL
? szDefault
: pEntry
->Value().c_str();
437 bool wxFileConfig::Read(wxString
*pstr
,
439 const char *szDefault
) const
441 PathChanger
path(this, szKey
);
443 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
444 if (pEntry
== NULL
) {
449 *pstr
= pEntry
->Value();
454 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
457 if ( Read(&str
, szKey
) ) {
467 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
469 PathChanger
path(this, szKey
);
471 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
472 if ( pEntry
== NULL
)
473 pEntry
= m_pCurrentGroup
->AddEntry(path
.Name());
474 pEntry
->SetValue(szValue
);
479 bool wxFileConfig::Write(const char *szKey
, long lValue
)
481 // ltoa() is not ANSI :-(
482 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
483 sprintf(szBuf
, "%ld", lValue
);
484 return Write(szKey
, szBuf
);
487 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
489 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
492 wxTempFile
file(m_strLocalFile
);
494 if ( !file
.IsOpened() ) {
495 wxLogError("Can't open user configuration file.");
499 // write all strings to file
500 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
501 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
502 wxLogError("Can't write user configuration file.");
507 return file
.Commit();
510 // ----------------------------------------------------------------------------
511 // delete groups/entries
512 // ----------------------------------------------------------------------------
514 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
516 PathChanger
path(this, szKey
);
518 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
521 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
522 if ( m_pCurrentGroup
!= m_pRootGroup
) {
523 ConfigGroup
*pGroup
= m_pCurrentGroup
;
524 SetPath(".."); // changes m_pCurrentGroup!
525 m_pCurrentGroup
->DeleteSubgroup(pGroup
->Name());
527 //else: never delete the root group
533 bool wxFileConfig::DeleteGroup(const char *szKey
)
535 PathChanger
path(this, szKey
);
537 return m_pCurrentGroup
->DeleteSubgroup(path
.Name());
540 bool wxFileConfig::DeleteAll()
542 const char *szFile
= m_strLocalFile
;
546 if ( remove(szFile
) == -1 )
547 wxLogSysError("Can't delete user configuration file '%s'", szFile
);
549 szFile
= m_strGlobalFile
;
550 if ( remove(szFile
) )
551 wxLogSysError("Can't delete system configuration file '%s'", szFile
);
556 // ----------------------------------------------------------------------------
557 // linked list functions
558 // ----------------------------------------------------------------------------
560 // append a new line to the end of the list
561 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
563 LineList
*pLine
= new LineList(str
);
565 if ( m_linesTail
== NULL
) {
571 m_linesTail
->SetNext(pLine
);
572 pLine
->SetPrev(m_linesTail
);
579 // insert a new line after the given one or in the very beginning if !pLine
580 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
583 if ( pLine
== m_linesTail
)
584 return LineListAppend(str
);
586 LineList
*pNewLine
= new LineList(str
);
587 if ( pLine
== NULL
) {
588 // prepend to the list
589 pNewLine
->SetNext(m_linesHead
);
590 m_linesHead
->SetPrev(pNewLine
);
591 m_linesHead
= pNewLine
;
594 // insert before pLine
595 LineList
*pNext
= pLine
->Next();
596 pNewLine
->SetNext(pNext
);
597 pNewLine
->SetPrev(pLine
);
598 pNext
->SetPrev(pNewLine
);
599 pLine
->SetNext(pNewLine
);
605 void wxFileConfig::LineListRemove(LineList
*pLine
)
607 LineList
*pPrev
= pLine
->Prev(),
608 *pNext
= pLine
->Next();
609 if ( pPrev
== NULL
) {
610 // deleting the first entry
614 // not the first entry
615 pPrev
->SetNext(pNext
);
618 pNext
->SetPrev(pPrev
);
623 bool wxFileConfig::LineListIsEmpty()
625 return m_linesHead
== NULL
;
628 // ============================================================================
629 // wxFileConfig::ConfigGroup
630 // ============================================================================
632 // ----------------------------------------------------------------------------
634 // ----------------------------------------------------------------------------
637 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
638 const wxString
& strName
,
639 wxFileConfig
*pConfig
)
650 // dtor deletes all children
651 wxFileConfig::ConfigGroup::~ConfigGroup()
654 uint n
, nCount
= m_aEntries
.Count();
655 for ( n
= 0; n
< nCount
; n
++ )
656 delete m_aEntries
[n
];
659 nCount
= m_aSubgroups
.Count();
660 for ( n
= 0; n
< nCount
; n
++ )
661 delete m_aSubgroups
[n
];
664 // ----------------------------------------------------------------------------
666 // ----------------------------------------------------------------------------
668 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
670 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
675 // return the line which contains "[our name]"
676 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
678 if ( m_pLine
== NULL
) {
679 // this group wasn't present in local config file, add it now
680 if ( Parent() != NULL
) {
681 wxString strFullName
;
682 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
683 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
684 Parent()->GetLastGroupLine());
685 Parent()->SetLastGroup(this);
688 // we return NULL, so that LineListInsert() will insert us in the
696 // return the last line belonging to the subgroups of this group
697 // (after which we can add a new subgroup)
698 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
700 // if we have any subgroups, our last line is the last line of the last
702 if ( m_pLastGroup
!= NULL
)
703 return m_pLastGroup
->GetLastGroupLine();
705 // if we have any entries, our last line is the last entry
706 if ( m_pLastEntry
!= NULL
)
707 return m_pLastEntry
->GetLine();
709 // nothing at all: last line is the first one
710 return GetGroupLine();
713 // return the last line belonging to the entries of this group
714 // (after which we can add a new entry)
715 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
717 if ( m_pLastEntry
!= NULL
) {
718 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
720 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
724 // no entrues: insert after the group header
725 return GetGroupLine();
728 // ----------------------------------------------------------------------------
730 // ----------------------------------------------------------------------------
732 wxString
wxFileConfig::ConfigGroup::GetFullName() const
735 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR
+ Name();
740 // ----------------------------------------------------------------------------
742 // ----------------------------------------------------------------------------
744 wxFileConfig::ConfigEntry
*
745 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
747 uint nCount
= m_aEntries
.Count();
748 for ( uint n
= 0; n
< nCount
; n
++ ) {
749 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
750 return m_aEntries
[n
];
756 wxFileConfig::ConfigGroup
*
757 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
759 uint nCount
= m_aSubgroups
.Count();
760 for ( uint n
= 0; n
< nCount
; n
++ ) {
761 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
762 return m_aSubgroups
[n
];
768 // ----------------------------------------------------------------------------
770 // ----------------------------------------------------------------------------
772 // create a new entry and add it to the current group
773 wxFileConfig::ConfigEntry
*
774 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
776 wxASSERT( FindEntry(strName
) == NULL
);
778 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
779 m_aEntries
.Add(pEntry
);
784 // create a new group and add it to the current group
785 wxFileConfig::ConfigGroup
*
786 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
788 wxASSERT( FindSubgroup(strName
) == NULL
);
790 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
791 m_aSubgroups
.Add(pGroup
);
796 // ----------------------------------------------------------------------------
798 // ----------------------------------------------------------------------------
800 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
802 uint n
, nCount
= m_aSubgroups
.Count();
803 for ( n
= 0; n
< nCount
; n
++ ) {
804 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
811 nCount
= m_aEntries
.Count();
812 for ( n
= 0; n
< nCount
; n
++ ) {
813 LineList
*pLine
= m_aEntries
[n
]->GetLine();
815 m_pConfig
->LineListRemove(pLine
);
818 ConfigGroup
*pGroup
= m_aSubgroups
[n
];
819 LineList
*pLine
= pGroup
->m_pLine
;
821 m_pConfig
->LineListRemove(pLine
);
826 m_aSubgroups
.Remove(n
);
830 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
832 uint n
, nCount
= m_aEntries
.Count();
833 for ( n
= 0; n
< nCount
; n
++ ) {
834 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
841 ConfigEntry
*pEntry
= m_aEntries
[n
];
842 LineList
*pLine
= pEntry
->GetLine();
844 m_pConfig
->LineListRemove(pLine
);
849 m_aEntries
.Remove(n
);
853 // ----------------------------------------------------------------------------
855 // ----------------------------------------------------------------------------
856 void wxFileConfig::ConfigGroup::SetDirty()
859 if ( Parent() != NULL
) // propagate upwards
860 Parent()->SetDirty();
863 // ============================================================================
864 // wxFileConfig::ConfigEntry
865 // ============================================================================
867 // ----------------------------------------------------------------------------
869 // ----------------------------------------------------------------------------
870 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
871 const wxString
& strName
,
881 m_bImmutable
= strName
[0] == APPCONF_IMMUTABLE_PREFIX
;
883 m_strName
.erase(0, 1); // remove first character
886 // ----------------------------------------------------------------------------
888 // ----------------------------------------------------------------------------
890 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
892 if ( m_pLine
!= NULL
) {
893 wxLogWarning("Entry '%s' appears more than once in group '%s'",
894 Name().c_str(), m_pParent
->GetFullName().c_str());
898 Group()->SetLastEntry(this);
901 // second parameter is FALSE if we read the value from file and prevents the
902 // entry from being marked as 'dirty'
903 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
905 if ( bUser
&& IsImmutable() ) {
906 wxLogWarning("Attempt to change immutable key '%s' ignored.",
911 // do nothing if it's the same value
912 if ( strValue
== m_strValue
)
915 m_strValue
= strValue
;
918 wxString strVal
= FilterOut(strValue
);
920 strLine
<< m_strName
<< " = " << strVal
;
922 if ( m_pLine
!= NULL
) {
923 // entry was read from the local config file, just modify the line
924 m_pLine
->SetText(strLine
);
927 // add a new line to the file
928 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
930 m_pLine
= Group()->Config()->LineListInsert(strLine
,
931 Group()->GetLastEntryLine());
932 Group()->SetLastEntry(this);
939 void wxFileConfig::ConfigEntry::SetDirty()
945 // ============================================================================
947 // ============================================================================
950 wxString
FilterIn(const wxString
& str
)
954 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
956 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
957 if ( str
[n
] == '\\' ) {
958 switch ( str
[++n
] ) {
977 if ( str
[n
] != '"' || !bQuoted
)
979 else if ( n
!= str
.Len() - 1 )
980 wxLogWarning("unexpected \" at position %d in '%s'.", n
, str
.c_str());
981 //else: it's the last quote of a quoted string, ok
988 // quote the string before writing it to file
989 wxString
FilterOut(const wxString
& str
)
993 // quoting is necessary to preserve spaces in the beginning of the string
994 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1000 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1017 //else: fall through
1020 strResult
+= str
[n
];
1021 continue; // nothing special to do
1024 // we get here only for special characters
1025 strResult
<< '\\' << c
;