]>
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()
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() ) {
333 if ( strPath
[0] == APPCONF_PATH_SEPARATOR
) {
335 SplitPath(aParts
, strPath
);
338 // relative path, combine with current one
339 wxString strFullPath
= m_strPath
;
340 strFullPath
<< APPCONF_PATH_SEPARATOR
<< strPath
;
341 SplitPath(aParts
, strFullPath
);
344 // change current group
346 m_pCurrentGroup
= m_pRootGroup
;
347 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
348 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
349 if ( pNextGroup
== NULL
)
350 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
351 m_pCurrentGroup
= pNextGroup
;
354 // recombine path parts in one variable
356 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
357 m_strPath
<< APPCONF_PATH_SEPARATOR
<< aParts
[n
];
361 // ----------------------------------------------------------------------------
363 // ----------------------------------------------------------------------------
365 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
)
368 return GetNextGroup(str
, lIndex
);
371 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
)
373 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
374 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
381 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
)
384 return GetNextEntry(str
, lIndex
);
387 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
)
389 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
390 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
397 // ----------------------------------------------------------------------------
398 // tests for existence
399 // ----------------------------------------------------------------------------
401 bool wxFileConfig::HasGroup(const wxString
& strName
) const
403 PathChanger
path(this, strName
);
405 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
406 return pGroup
!= NULL
;
409 bool wxFileConfig::HasEntry(const wxString
& strName
) const
411 PathChanger
path(this, strName
);
413 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
414 return pEntry
!= NULL
;
417 // ----------------------------------------------------------------------------
419 // ----------------------------------------------------------------------------
421 const char *wxFileConfig::Read(const char *szKey
,
422 const char *szDefault
) const
424 PathChanger
path(this, szKey
);
426 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
427 return pEntry
== NULL
? szDefault
: pEntry
->Value().c_str();
430 bool wxFileConfig::Read(wxString
*pstr
,
432 const char *szDefault
) const
434 PathChanger
path(this, szKey
);
436 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
437 if (pEntry
== NULL
) {
442 *pstr
= pEntry
->Value();
447 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
450 if ( Read(&str
, szKey
) ) {
460 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
462 PathChanger
path(this, szKey
);
464 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
465 if ( pEntry
== NULL
)
466 pEntry
= m_pCurrentGroup
->AddEntry(path
.Name());
467 pEntry
->SetValue(szValue
);
472 bool wxFileConfig::Write(const char *szKey
, long lValue
)
474 // ltoa() is not ANSI :-(
475 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
476 sprintf(szBuf
, "%ld", lValue
);
477 return Write(szKey
, szBuf
);
480 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
482 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
485 wxTempFile
file(m_strLocalFile
);
487 if ( !file
.IsOpened() ) {
488 wxLogError("Can't open user configuration file.");
492 // write all strings to file
493 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
494 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
495 wxLogError("Can't write user configuration file.");
500 return file
.Commit();
503 // ----------------------------------------------------------------------------
504 // delete groups/entries
505 // ----------------------------------------------------------------------------
507 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
509 PathChanger
path(this, szKey
);
511 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
514 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
515 if ( m_pCurrentGroup
!= m_pRootGroup
) {
516 ConfigGroup
*pGroup
= m_pCurrentGroup
;
517 SetPath(".."); // changes m_pCurrentGroup!
518 m_pCurrentGroup
->DeleteSubgroup(pGroup
->Name());
520 //else: never delete the root group
526 bool wxFileConfig::DeleteGroup(const char *szKey
)
528 PathChanger
path(this, szKey
);
530 return m_pCurrentGroup
->DeleteSubgroup(path
.Name());
533 bool wxFileConfig::DeleteAll()
535 const char *szFile
= m_strLocalFile
;
539 if ( remove(szFile
) == -1 )
540 wxLogSysError("Can't delete user configuration file '%s'", szFile
);
542 szFile
= m_strGlobalFile
;
543 if ( remove(szFile
) )
544 wxLogSysError("Can't delete system configuration file '%s'", szFile
);
549 // ----------------------------------------------------------------------------
550 // linked list functions
551 // ----------------------------------------------------------------------------
553 // append a new line to the end of the list
554 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
556 LineList
*pLine
= new LineList(str
);
558 if ( m_linesTail
== NULL
) {
564 m_linesTail
->SetNext(pLine
);
565 pLine
->SetPrev(m_linesTail
);
572 // insert a new line after the given one or in the very beginning if !pLine
573 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
576 if ( pLine
== m_linesTail
)
577 return LineListAppend(str
);
579 LineList
*pNewLine
= new LineList(str
);
580 if ( pLine
== NULL
) {
581 // prepend to the list
582 pNewLine
->SetNext(m_linesHead
);
583 m_linesHead
->SetPrev(pNewLine
);
584 m_linesHead
= pNewLine
;
587 // insert before pLine
588 LineList
*pNext
= pLine
->Next();
589 pNewLine
->SetNext(pNext
);
590 pNewLine
->SetPrev(pLine
);
591 pNext
->SetPrev(pNewLine
);
592 pLine
->SetNext(pNewLine
);
598 void wxFileConfig::LineListRemove(LineList
*pLine
)
600 LineList
*pPrev
= pLine
->Prev(),
601 *pNext
= pLine
->Next();
602 if ( pPrev
== NULL
) {
603 // deleting the first entry
607 // not the first entry
608 pPrev
->SetNext(pNext
);
611 pNext
->SetPrev(pPrev
);
616 bool wxFileConfig::LineListIsEmpty()
618 return m_linesHead
== NULL
;
621 // ============================================================================
622 // wxFileConfig::ConfigGroup
623 // ============================================================================
625 // ----------------------------------------------------------------------------
627 // ----------------------------------------------------------------------------
630 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
631 const wxString
& strName
,
632 wxFileConfig
*pConfig
)
643 // dtor deletes all children
644 wxFileConfig::ConfigGroup::~ConfigGroup()
647 uint n
, nCount
= m_aEntries
.Count();
648 for ( n
= 0; n
< nCount
; n
++ )
649 delete m_aEntries
[n
];
652 nCount
= m_aSubgroups
.Count();
653 for ( n
= 0; n
< nCount
; n
++ )
654 delete m_aSubgroups
[n
];
657 // ----------------------------------------------------------------------------
659 // ----------------------------------------------------------------------------
661 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
663 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
668 // return the line which contains "[our name]"
669 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
671 if ( m_pLine
== NULL
) {
672 // this group wasn't present in local config file, add it now
673 if ( Parent() != NULL
) {
674 wxString strFullName
;
675 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
676 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
677 Parent()->GetLastGroupLine());
678 Parent()->SetLastGroup(this);
681 // we return NULL, so that LineListInsert() will insert us in the
689 // return the last line belonging to the subgroups of this group
690 // (after which we can add a new subgroup)
691 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
693 // if we have any subgroups, our last line is the last line of the last
695 if ( m_pLastGroup
!= NULL
)
696 return m_pLastGroup
->GetLastGroupLine();
698 // if we have any entries, our last line is the last entry
699 if ( m_pLastEntry
!= NULL
)
700 return m_pLastEntry
->GetLine();
702 // nothing at all: last line is the first one
703 return GetGroupLine();
706 // return the last line belonging to the entries of this group
707 // (after which we can add a new entry)
708 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
710 if ( m_pLastEntry
!= NULL
) {
711 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
713 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
717 // no entrues: insert after the group header
718 return GetGroupLine();
721 // ----------------------------------------------------------------------------
723 // ----------------------------------------------------------------------------
725 wxString
wxFileConfig::ConfigGroup::GetFullName() const
728 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR
+ Name();
733 // ----------------------------------------------------------------------------
735 // ----------------------------------------------------------------------------
737 wxFileConfig::ConfigEntry
*
738 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
740 uint nCount
= m_aEntries
.Count();
741 for ( uint n
= 0; n
< nCount
; n
++ ) {
742 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
743 return m_aEntries
[n
];
749 wxFileConfig::ConfigGroup
*
750 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
752 uint nCount
= m_aSubgroups
.Count();
753 for ( uint n
= 0; n
< nCount
; n
++ ) {
754 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
755 return m_aSubgroups
[n
];
761 // ----------------------------------------------------------------------------
763 // ----------------------------------------------------------------------------
765 // create a new entry and add it to the current group
766 wxFileConfig::ConfigEntry
*
767 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
769 wxASSERT( FindEntry(strName
) == NULL
);
771 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
772 m_aEntries
.Add(pEntry
);
777 // create a new group and add it to the current group
778 wxFileConfig::ConfigGroup
*
779 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
781 wxASSERT( FindSubgroup(strName
) == NULL
);
783 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
784 m_aSubgroups
.Add(pGroup
);
789 // ----------------------------------------------------------------------------
791 // ----------------------------------------------------------------------------
793 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
795 uint n
, nCount
= m_aSubgroups
.Count();
796 for ( n
= 0; n
< nCount
; n
++ ) {
797 if ( m_aSubgroups
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
804 nCount
= m_aEntries
.Count();
805 for ( n
= 0; n
< nCount
; n
++ ) {
806 LineList
*pLine
= m_aEntries
[n
]->GetLine();
808 m_pConfig
->LineListRemove(pLine
);
811 ConfigGroup
*pGroup
= m_aSubgroups
[n
];
812 LineList
*pLine
= pGroup
->m_pLine
;
814 m_pConfig
->LineListRemove(pLine
);
819 m_aSubgroups
.Remove(n
);
823 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
825 uint n
, nCount
= m_aEntries
.Count();
826 for ( n
= 0; n
< nCount
; n
++ ) {
827 if ( m_aEntries
[n
]->Name().IsSameAs(szName
, APPCONF_CASE_SENSITIVE
) )
834 ConfigEntry
*pEntry
= m_aEntries
[n
];
835 LineList
*pLine
= pEntry
->GetLine();
837 m_pConfig
->LineListRemove(pLine
);
842 m_aEntries
.Remove(n
);
846 // ----------------------------------------------------------------------------
848 // ----------------------------------------------------------------------------
849 void wxFileConfig::ConfigGroup::SetDirty()
852 if ( Parent() != NULL
) // propagate upwards
853 Parent()->SetDirty();
856 // ============================================================================
857 // wxFileConfig::ConfigEntry
858 // ============================================================================
860 // ----------------------------------------------------------------------------
862 // ----------------------------------------------------------------------------
863 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
864 const wxString
& strName
,
874 m_bImmutable
= strName
[0] == APPCONF_IMMUTABLE_PREFIX
;
876 m_strName
.erase(0, 1); // remove first character
879 // ----------------------------------------------------------------------------
881 // ----------------------------------------------------------------------------
883 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
885 if ( m_pLine
!= NULL
) {
886 wxLogWarning("Entry '%s' appears more than once in group '%s'",
887 Name().c_str(), m_pParent
->GetFullName().c_str());
891 Group()->SetLastEntry(this);
894 // second parameter is FALSE if we read the value from file and prevents the
895 // entry from being marked as 'dirty'
896 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
898 if ( bUser
&& IsImmutable() ) {
899 wxLogWarning("Attempt to change immutable key '%s' ignored.",
904 // do nothing if it's the same value
905 if ( strValue
== m_strValue
)
908 m_strValue
= strValue
;
911 wxString strVal
= FilterOut(strValue
);
913 strLine
<< m_strName
<< " = " << strVal
;
915 if ( m_pLine
!= NULL
) {
916 // entry was read from the local config file, just modify the line
917 m_pLine
->SetText(strLine
);
920 // add a new line to the file
921 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
923 m_pLine
= Group()->Config()->LineListInsert(strLine
,
924 Group()->GetLastEntryLine());
925 Group()->SetLastEntry(this);
932 void wxFileConfig::ConfigEntry::SetDirty()
938 // ============================================================================
940 // ============================================================================
943 wxString
FilterIn(const wxString
& str
)
947 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
949 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
950 if ( str
[n
] == '\\' ) {
951 switch ( str
[++n
] ) {
970 if ( str
[n
] != '"' || !bQuoted
)
972 else if ( n
!= str
.Len() - 1 )
973 wxLogWarning("unexpected \" at position %d in '%s'.", n
, str
.c_str());
974 //else: it's the last quote of a quoted string, ok
981 // quote the string before writing it to file
982 wxString
FilterOut(const wxString
& str
)
986 // quoting is necessary to preserve spaces in the beginning of the string
987 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
993 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1010 //else: fall through
1013 strResult
+= str
[n
];
1014 continue; // nothing special to do
1017 // we get here only for special characters
1018 strResult
<< '\\' << c
;