// This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1998 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "stdafx.h" #include #ifdef AFX_AUX_SEG #pragma code_seg(AFX_AUX_SEG) #endif #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW ////////////////////////////////////////////////////////////////////////////// // More sophisticated construction CString::CString(TCHAR ch, int nLength) { Init(); if (nLength >= 1) { AllocBuffer(nLength); #ifdef _UNICODE for (int i = 0; i < nLength; i++) m_pchData[i] = ch; #else memset(m_pchData, ch, nLength); #endif } } CString::CString(LPCTSTR lpch, int nLength) { Init(); if (nLength != 0) { ASSERT(AfxIsValidAddress(lpch, nLength, FALSE)); AllocBuffer(nLength); memcpy(m_pchData, lpch, nLength*sizeof(TCHAR)); } } ///////////////////////////////////////////////////////////////////////////// // Special conversion constructors #ifdef _UNICODE CString::CString(LPCSTR lpsz, int nLength) { Init(); if (nLength != 0) { AllocBuffer(nLength); int n = ::MultiByteToWideChar(CP_ACP, 0, lpsz, nLength, m_pchData, nLength+1); ReleaseBuffer(n >= 0 ? n : -1); } } #else //_UNICODE CString::CString(LPCWSTR lpsz, int nLength) { Init(); if (nLength != 0) { AllocBuffer(nLength*2); int n = ::WideCharToMultiByte(CP_ACP, 0, lpsz, nLength, m_pchData, (nLength*2)+1, NULL, NULL); ReleaseBuffer(n >= 0 ? n : -1); } } #endif //!_UNICODE ////////////////////////////////////////////////////////////////////////////// // Assignment operators const CString& CString::operator=(TCHAR ch) { AssignCopy(1, &ch); return *this; } ////////////////////////////////////////////////////////////////////////////// // less common string expressions CString AFXAPI operator+(const CString& string1, TCHAR ch) { CString s; s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData, 1, &ch); return s; } CString AFXAPI operator+(TCHAR ch, const CString& string) { CString s; s.ConcatCopy(1, &ch, string.GetData()->nDataLength, string.m_pchData); return s; } ////////////////////////////////////////////////////////////////////////////// // Advanced manipulation int CString::Delete(int nIndex, int nCount /* = 1 */) { if (nIndex < 0) nIndex = 0; int nNewLength = GetData()->nDataLength; if (nCount > 0 && nIndex < nNewLength) { CopyBeforeWrite(); int nBytesToCopy = nNewLength - (nIndex + nCount) + 1; memcpy(m_pchData + nIndex, m_pchData + nIndex + nCount, nBytesToCopy * sizeof(TCHAR)); GetData()->nDataLength = nNewLength - nCount; } return nNewLength; } int CString::Insert(int nIndex, TCHAR ch) { CopyBeforeWrite(); if (nIndex < 0) nIndex = 0; int nNewLength = GetData()->nDataLength; if (nIndex > nNewLength) nIndex = nNewLength; nNewLength++; if (GetData()->nAllocLength < nNewLength) { CStringData* pOldData = GetData(); LPTSTR pstr = m_pchData; AllocBuffer(nNewLength); memcpy(m_pchData, pstr, (pOldData->nDataLength+1)*sizeof(TCHAR)); CString::Release(pOldData); } // move existing bytes down memcpy(m_pchData + nIndex + 1, m_pchData + nIndex, (nNewLength-nIndex)*sizeof(TCHAR)); m_pchData[nIndex] = ch; GetData()->nDataLength = nNewLength; return nNewLength; } int CString::Insert(int nIndex, LPCTSTR pstr) { if (nIndex < 0) nIndex = 0; int nInsertLength = SafeStrlen(pstr); int nNewLength = GetData()->nDataLength; if (nInsertLength > 0) { CopyBeforeWrite(); if (nIndex > nNewLength) nIndex = nNewLength; nNewLength += nInsertLength; if (GetData()->nAllocLength < nNewLength) { CStringData* pOldData = GetData(); LPTSTR pstr = m_pchData; AllocBuffer(nNewLength); memcpy(m_pchData, pstr, (pOldData->nDataLength+1)*sizeof(TCHAR)); CString::Release(pOldData); } // move existing bytes down memcpy(m_pchData + nIndex + nInsertLength, m_pchData + nIndex, (nNewLength-nIndex-nInsertLength+1)*sizeof(TCHAR)); memcpy(m_pchData + nIndex, pstr, nInsertLength*sizeof(TCHAR)); GetData()->nDataLength = nNewLength; } return nNewLength; } int CString::Replace(TCHAR chOld, TCHAR chNew) { int nCount = 0; // short-circuit the nop case if (chOld != chNew) { // otherwise modify each character that matches in the string CopyBeforeWrite(); LPTSTR psz = m_pchData; LPTSTR pszEnd = psz + GetData()->nDataLength; while (psz < pszEnd) { // replace instances of the specified character only if (*psz == chOld) { *psz = chNew; nCount++; } psz = _tcsinc(psz); } } return nCount; } int CString::Replace(LPCTSTR lpszOld, LPCTSTR lpszNew) { // can't have empty or NULL lpszOld int nSourceLen = SafeStrlen(lpszOld); if (nSourceLen == 0) return 0; int nReplacementLen = SafeStrlen(lpszNew); // loop once to figure out the size of the result string int nCount = 0; LPTSTR lpszStart = m_pchData; LPTSTR lpszEnd = m_pchData + GetData()->nDataLength; LPTSTR lpszTarget; while (lpszStart < lpszEnd) { while ((lpszTarget = _tcsstr(lpszStart, lpszOld)) != NULL) { nCount++; lpszStart = lpszTarget + nSourceLen; } lpszStart += lstrlen(lpszStart) + 1; } // if any changes were made, make them if (nCount > 0) { CopyBeforeWrite(); // if the buffer is too small, just // allocate a new buffer (slow but sure) int nOldLength = GetData()->nDataLength; int nNewLength = nOldLength + (nReplacementLen-nSourceLen)*nCount; if (GetData()->nAllocLength < nNewLength || GetData()->nRefs > 1) { CStringData* pOldData = GetData(); LPTSTR pstr = m_pchData; AllocBuffer(nNewLength); memcpy(m_pchData, pstr, pOldData->nDataLength*sizeof(TCHAR)); CString::Release(pOldData); } // else, we just do it in-place lpszStart = m_pchData; lpszEnd = m_pchData + GetData()->nDataLength; // loop again to actually do the work while (lpszStart < lpszEnd) { while ( (lpszTarget = _tcsstr(lpszStart, lpszOld)) != NULL) { int nBalance = nOldLength - (lpszTarget - m_pchData + nSourceLen); memmove(lpszTarget + nReplacementLen, lpszTarget + nSourceLen, nBalance * sizeof(TCHAR)); memcpy(lpszTarget, lpszNew, nReplacementLen*sizeof(TCHAR)); lpszStart = lpszTarget + nReplacementLen; lpszStart[nBalance] = '\0'; nOldLength += (nReplacementLen - nSourceLen); } lpszStart += lstrlen(lpszStart) + 1; } ASSERT(m_pchData[nNewLength] == '\0'); GetData()->nDataLength = nNewLength; } return nCount; } int CString::Remove(TCHAR chRemove) { CopyBeforeWrite(); LPTSTR pstrSource = m_pchData; LPTSTR pstrDest = m_pchData; LPTSTR pstrEnd = m_pchData + GetData()->nDataLength; while (pstrSource < pstrEnd) { if (*pstrSource != chRemove) { *pstrDest = *pstrSource; pstrDest = _tcsinc(pstrDest); } pstrSource = _tcsinc(pstrSource); } *pstrDest = '\0'; int nCount = pstrSource - pstrDest; GetData()->nDataLength -= nCount; return nCount; } ////////////////////////////////////////////////////////////////////////////// // Very simple sub-string extraction CString CString::Mid(int nFirst) const { return Mid(nFirst, GetData()->nDataLength - nFirst); } CString CString::Mid(int nFirst, int nCount) const { // out-of-bounds requests return sensible things if (nFirst < 0) nFirst = 0; if (nCount < 0) nCount = 0; if (nFirst + nCount > GetData()->nDataLength) nCount = GetData()->nDataLength - nFirst; if (nFirst > GetData()->nDataLength) nCount = 0; ASSERT(nFirst >= 0); ASSERT(nFirst + nCount <= GetData()->nDataLength); // optimize case of returning entire string if (nFirst == 0 && nFirst + nCount == GetData()->nDataLength) return *this; CString dest; AllocCopy(dest, nCount, nFirst, 0); return dest; } CString CString::Right(int nCount) const { if (nCount < 0) nCount = 0; if (nCount >= GetData()->nDataLength) return *this; CString dest; AllocCopy(dest, nCount, GetData()->nDataLength-nCount, 0); return dest; } CString CString::Left(int nCount) const { if (nCount < 0) nCount = 0; if (nCount >= GetData()->nDataLength) return *this; CString dest; AllocCopy(dest, nCount, 0, 0); return dest; } // strspn equivalent CString CString::SpanIncluding(LPCTSTR lpszCharSet) const { ASSERT(AfxIsValidString(lpszCharSet)); return Left(_tcsspn(m_pchData, lpszCharSet)); } // strcspn equivalent CString CString::SpanExcluding(LPCTSTR lpszCharSet) const { ASSERT(AfxIsValidString(lpszCharSet)); return Left(_tcscspn(m_pchData, lpszCharSet)); } ////////////////////////////////////////////////////////////////////////////// // Finding int CString::ReverseFind(TCHAR ch) const { // find last single character LPTSTR lpsz = _tcsrchr(m_pchData, (_TUCHAR) ch); // return -1 if not found, distance from beginning otherwise return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); } // find a sub-string (like strstr) int CString::Find(LPCTSTR lpszSub) const { return Find(lpszSub, 0); } int CString::Find(LPCTSTR lpszSub, int nStart) const { ASSERT(AfxIsValidString(lpszSub)); int nLength = GetData()->nDataLength; if (nStart > nLength) return -1; // find first matching substring LPTSTR lpsz = _tcsstr(m_pchData + nStart, lpszSub); // return -1 for not found, distance from beginning otherwise return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); } ///////////////////////////////////////////////////////////////////////////// // CString formatting #define TCHAR_ARG TCHAR #define WCHAR_ARG WCHAR #define CHAR_ARG char #ifdef _X86_ #define DOUBLE_ARG _AFX_DOUBLE #else #define DOUBLE_ARG double #endif #define FORCE_ANSI 0x10000 #define FORCE_UNICODE 0x20000 #define FORCE_INT64 0x40000 void CString::FormatV(LPCTSTR lpszFormat, va_list argList) { ASSERT(AfxIsValidString(lpszFormat)); va_list argListSave = argList; // make a guess at the maximum length of the resulting string int nMaxLen = 0; for (LPCTSTR lpsz = lpszFormat; *lpsz != '\0'; lpsz = _tcsinc(lpsz)) { // handle '%' character, but watch out for '%%' if (*lpsz != '%' || *(lpsz = _tcsinc(lpsz)) == '%') { nMaxLen += _tclen(lpsz); continue; } int nItemLen = 0; // handle '%' character with format int nWidth = 0; for (; *lpsz != '\0'; lpsz = _tcsinc(lpsz)) { // check for valid flags if (*lpsz == '#') nMaxLen += 2; // for '0x' else if (*lpsz == '*') nWidth = va_arg(argList, int); else if (*lpsz == '-' || *lpsz == '+' || *lpsz == '0' || *lpsz == ' ') ; else // hit non-flag character break; } // get width and skip it if (nWidth == 0) { // width indicated by nWidth = _ttoi(lpsz); for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz)) ; } ASSERT(nWidth >= 0); int nPrecision = 0; if (*lpsz == '.') { // skip past '.' separator (width.precision) lpsz = _tcsinc(lpsz); // get precision and skip it if (*lpsz == '*') { nPrecision = va_arg(argList, int); lpsz = _tcsinc(lpsz); } else { nPrecision = _ttoi(lpsz); for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz)) ; } ASSERT(nPrecision >= 0); } // should be on type modifier or specifier int nModifier = 0; if (_tcsncmp(lpsz, _T("I64"), 3) == 0) { lpsz += 3; nModifier = FORCE_INT64; #if !defined(_X86_) && !defined(_ALPHA_) // __int64 is only available on X86 and ALPHA platforms ASSERT(FALSE); #endif } else { switch (*lpsz) { // modifiers that affect size case 'h': nModifier = FORCE_ANSI; lpsz = _tcsinc(lpsz); break; case 'l': nModifier = FORCE_UNICODE; lpsz = _tcsinc(lpsz); break; // modifiers that do not affect size case 'F': case 'N': case 'L': lpsz = _tcsinc(lpsz); break; } } // now should be on specifier switch (*lpsz | nModifier) { // single characters case 'c': case 'C': nItemLen = 2; va_arg(argList, TCHAR_ARG); break; case 'c'|FORCE_ANSI: case 'C'|FORCE_ANSI: nItemLen = 2; va_arg(argList, CHAR_ARG); break; case 'c'|FORCE_UNICODE: case 'C'|FORCE_UNICODE: nItemLen = 2; va_arg(argList, WCHAR_ARG); break; // strings case 's': { LPCTSTR pstrNextArg = va_arg(argList, LPCTSTR); if (pstrNextArg == NULL) nItemLen = 6; // "(null)" else { nItemLen = lstrlen(pstrNextArg); nItemLen = max(1, nItemLen); } } break; case 'S': { #ifndef _UNICODE LPWSTR pstrNextArg = va_arg(argList, LPWSTR); if (pstrNextArg == NULL) nItemLen = 6; // "(null)" else { nItemLen = wcslen(pstrNextArg); nItemLen = max(1, nItemLen); } #else LPCSTR pstrNextArg = va_arg(argList, LPCSTR); if (pstrNextArg == NULL) nItemLen = 6; // "(null)" else { nItemLen = lstrlenA(pstrNextArg); nItemLen = max(1, nItemLen); } #endif } break; case 's'|FORCE_ANSI: case 'S'|FORCE_ANSI: { LPCSTR pstrNextArg = va_arg(argList, LPCSTR); if (pstrNextArg == NULL) nItemLen = 6; // "(null)" else { nItemLen = lstrlenA(pstrNextArg); nItemLen = max(1, nItemLen); } } break; case 's'|FORCE_UNICODE: case 'S'|FORCE_UNICODE: { LPWSTR pstrNextArg = va_arg(argList, LPWSTR); if (pstrNextArg == NULL) nItemLen = 6; // "(null)" else { nItemLen = wcslen(pstrNextArg); nItemLen = max(1, nItemLen); } } break; } // adjust nItemLen for strings if (nItemLen != 0) { if (nPrecision != 0) nItemLen = min(nItemLen, nPrecision); nItemLen = max(nItemLen, nWidth); } else { switch (*lpsz) { // integers case 'd': case 'i': case 'u': case 'x': case 'X': case 'o': if (nModifier & FORCE_INT64) va_arg(argList, __int64); else va_arg(argList, int); nItemLen = 32; nItemLen = max(nItemLen, nWidth+nPrecision); break; case 'e': case 'g': case 'G': va_arg(argList, DOUBLE_ARG); nItemLen = 128; nItemLen = max(nItemLen, nWidth+nPrecision); break; case 'f': va_arg(argList, DOUBLE_ARG); nItemLen = 128; // width isn't truncated // 312 == strlen("-1+(309 zeroes).") // 309 zeroes == max precision of a double nItemLen = max(nItemLen, 312+nPrecision); break; case 'p': va_arg(argList, void*); nItemLen = 32; nItemLen = max(nItemLen, nWidth+nPrecision); break; // no output case 'n': va_arg(argList, int*); break; default: ASSERT(FALSE); // unknown formatting option } } // adjust nMaxLen for output nItemLen nMaxLen += nItemLen; } GetBuffer(nMaxLen); VERIFY(_vstprintf(m_pchData, lpszFormat, argListSave) <= GetAllocLength()); ReleaseBuffer(); va_end(argListSave); } // formatting (using wsprintf style formatting) void AFX_CDECL CString::Format(LPCTSTR lpszFormat, ...) { ASSERT(AfxIsValidString(lpszFormat)); va_list argList; va_start(argList, lpszFormat); FormatV(lpszFormat, argList); va_end(argList); } void AFX_CDECL CString::Format(UINT nFormatID, ...) { CString strFormat; VERIFY(strFormat.LoadString(nFormatID) != 0); va_list argList; va_start(argList, nFormatID); FormatV(strFormat, argList); va_end(argList); } // formatting (using FormatMessage style formatting) void AFX_CDECL CString::FormatMessage(LPCTSTR lpszFormat, ...) { // format message into temporary buffer lpszTemp va_list argList; va_start(argList, lpszFormat); LPTSTR lpszTemp; if (::FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, lpszFormat, 0, 0, (LPTSTR)&lpszTemp, 0, &argList) == 0 || lpszTemp == NULL) { AfxThrowMemoryException(); } // assign lpszTemp into the resulting string and free the temporary *this = lpszTemp; LocalFree(lpszTemp); va_end(argList); } void AFX_CDECL CString::FormatMessage(UINT nFormatID, ...) { // get format string from string table CString strFormat; VERIFY(strFormat.LoadString(nFormatID) != 0); // format message into temporary buffer lpszTemp va_list argList; va_start(argList, nFormatID); LPTSTR lpszTemp; if (::FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, strFormat, 0, 0, (LPTSTR)&lpszTemp, 0, &argList) == 0 || lpszTemp == NULL) { AfxThrowMemoryException(); } // assign lpszTemp into the resulting string and free lpszTemp *this = lpszTemp; LocalFree(lpszTemp); va_end(argList); } void CString::TrimRight(LPCTSTR lpszTargetList) { // find beginning of trailing matches // by starting at beginning (DBCS aware) CopyBeforeWrite(); LPTSTR lpsz = m_pchData; LPTSTR lpszLast = NULL; while (*lpsz != '\0') { if (_tcschr(lpszTargetList, *lpsz) != NULL) { if (lpszLast == NULL) lpszLast = lpsz; } else lpszLast = NULL; lpsz = _tcsinc(lpsz); } if (lpszLast != NULL) { // truncate at left-most matching character *lpszLast = '\0'; GetData()->nDataLength = lpszLast - m_pchData; } } void CString::TrimRight(TCHAR chTarget) { // find beginning of trailing matches // by starting at beginning (DBCS aware) CopyBeforeWrite(); LPTSTR lpsz = m_pchData; LPTSTR lpszLast = NULL; while (*lpsz != '\0') { if (*lpsz == chTarget) { if (lpszLast == NULL) lpszLast = lpsz; } else lpszLast = NULL; lpsz = _tcsinc(lpsz); } if (lpszLast != NULL) { // truncate at left-most matching character *lpszLast = '\0'; GetData()->nDataLength = lpszLast - m_pchData; } } void CString::TrimRight() { // find beginning of trailing spaces by starting at beginning (DBCS aware) CopyBeforeWrite(); LPTSTR lpsz = m_pchData; LPTSTR lpszLast = NULL; while (*lpsz != '\0') { if (_istspace(*lpsz)) { if (lpszLast == NULL) lpszLast = lpsz; } else lpszLast = NULL; lpsz = _tcsinc(lpsz); } if (lpszLast != NULL) { // truncate at trailing space start *lpszLast = '\0'; GetData()->nDataLength = lpszLast - m_pchData; } } void CString::TrimLeft(LPCTSTR lpszTargets) { // if we're not trimming anything, we're not doing any work if (SafeStrlen(lpszTargets) == 0) return; CopyBeforeWrite(); LPCTSTR lpsz = m_pchData; while (*lpsz != '\0') { if (_tcschr(lpszTargets, *lpsz) == NULL) break; lpsz = _tcsinc(lpsz); } if (lpsz != m_pchData) { // fix up data and length int nDataLength = GetData()->nDataLength - (lpsz - m_pchData); memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR)); GetData()->nDataLength = nDataLength; } } void CString::TrimLeft(TCHAR chTarget) { // find first non-matching character CopyBeforeWrite(); LPCTSTR lpsz = m_pchData; while (chTarget == *lpsz) lpsz = _tcsinc(lpsz); if (lpsz != m_pchData) { // fix up data and length int nDataLength = GetData()->nDataLength - (lpsz - m_pchData); memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR)); GetData()->nDataLength = nDataLength; } } void CString::TrimLeft() { // find first non-space character CopyBeforeWrite(); LPCTSTR lpsz = m_pchData; while (_istspace(*lpsz)) lpsz = _tcsinc(lpsz); if (lpsz != m_pchData) { // fix up data and length int nDataLength = GetData()->nDataLength - (lpsz - m_pchData); memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR)); GetData()->nDataLength = nDataLength; } } /////////////////////////////////////////////////////////////////////////////// // CString support for template collections #if _MSC_VER >= 1100 template<> void AFXAPI ConstructElements (CString* pElements, int nCount) #else void AFXAPI ConstructElements(CString* pElements, int nCount) #endif { ASSERT(nCount == 0 || AfxIsValidAddress(pElements, nCount * sizeof(CString))); for (; nCount--; ++pElements) memcpy(pElements, &afxEmptyString, sizeof(*pElements)); } #if _MSC_VER >= 1100 template<> void AFXAPI DestructElements (CString* pElements, int nCount) #else void AFXAPI DestructElements(CString* pElements, int nCount) #endif { ASSERT(nCount == 0 || AfxIsValidAddress(pElements, nCount * sizeof(CString))); for (; nCount--; ++pElements) pElements->~CString(); } #if _MSC_VER >= 1100 template<> void AFXAPI CopyElements (CString* pDest, const CString* pSrc, int nCount) #else void AFXAPI CopyElements(CString* pDest, const CString* pSrc, int nCount) #endif { ASSERT(nCount == 0 || AfxIsValidAddress(pDest, nCount * sizeof(CString))); ASSERT(nCount == 0 || AfxIsValidAddress(pSrc, nCount * sizeof(CString))); for (; nCount--; ++pDest, ++pSrc) *pDest = *pSrc; } #ifndef OLE2ANSI #if _MSC_VER >= 1100 template<> UINT AFXAPI HashKey (LPCWSTR key) #else UINT AFXAPI HashKey(LPCWSTR key) #endif { UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash; } #endif #if _MSC_VER >= 1100 template<> UINT AFXAPI HashKey (LPCSTR key) #else UINT AFXAPI HashKey(LPCSTR key) #endif { UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash; } ///////////////////////////////////////////////////////////////////////////////