1568 lines
36 KiB
C++
1568 lines
36 KiB
C++
|
/*++
|
||
|
|
||
|
Copyright (c) 1998 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
bsstring.cpp
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
This module implements the CBsString class. This class manages character
|
||
|
arrays in a similar manner as the CString class in VC++. In fact, this
|
||
|
class is a copy of the CString class with the MFC specific stuff ripped
|
||
|
out since LTS doesn't use MTF.
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Stefan R. Steiner [SSteiner] 1-Mar-1998
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Stefan R. Steiner [SSteiner] 10-Apr-2000
|
||
|
Added fixed allocator code and resynced with MFC 6 SR-1 code
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
#include "bsstring.h"
|
||
|
#include "malloc.h"
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
#undef THIS_FILE
|
||
|
static char THIS_FILE[] = __FILE__;
|
||
|
#endif
|
||
|
|
||
|
struct _BSAFX_DOUBLE { BYTE doubleBits[sizeof(double)]; };
|
||
|
struct _BSAFX_FLOAT { BYTE floatBits[sizeof(float)]; };
|
||
|
|
||
|
// #define new DEBUG_NEW
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// static class data, special inlines
|
||
|
|
||
|
TCHAR bsafxChNil = '\0';
|
||
|
|
||
|
// For an empty string, m_pchData will point here
|
||
|
// (note: avoids special case of checking for NULL m_pchData)
|
||
|
// empty string data (and locked)
|
||
|
static int _bsafxInitData[] = { -1, 0, 0, 0 };
|
||
|
static CBsStringData* _bsafxDataNil = (CBsStringData*)&_bsafxInitData;
|
||
|
static LPCTSTR _bsafxPchNil = (LPCTSTR)(((BYTE*)&_bsafxInitData)+sizeof(CBsStringData));
|
||
|
// special function to make bsafxEmptyString work even during initialization
|
||
|
const CBsString& BSAFXAPI BsAfxGetEmptyString()
|
||
|
{ return *(CBsString*)&_bsafxPchNil; }
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Construction/Destruction
|
||
|
|
||
|
CBsString::CBsString()
|
||
|
{
|
||
|
Init();
|
||
|
}
|
||
|
|
||
|
CBsString::CBsString(const CBsString& stringSrc)
|
||
|
{
|
||
|
ASSERT(stringSrc.GetData()->nRefs != 0);
|
||
|
if (stringSrc.GetData()->nRefs >= 0)
|
||
|
{
|
||
|
ASSERT(stringSrc.GetData() != _bsafxDataNil);
|
||
|
m_pchData = stringSrc.m_pchData;
|
||
|
InterlockedIncrement(&GetData()->nRefs);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Init();
|
||
|
*this = stringSrc.m_pchData;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CBsString::CBsString(GUID guid)
|
||
|
{
|
||
|
Init();
|
||
|
AllocBuffer(38);
|
||
|
_stprintf( m_pchData, _T("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}"),
|
||
|
guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1],
|
||
|
guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
|
||
|
guid.Data4[6], guid.Data4[7] );
|
||
|
}
|
||
|
|
||
|
#ifndef _DEBUG
|
||
|
|
||
|
#pragma warning(disable: 4074)
|
||
|
#pragma init_seg(compiler)
|
||
|
|
||
|
#define ROUND(x,y) (((x)+(y-1))&~(y-1))
|
||
|
#define ROUND4(x) ROUND(x, 4)
|
||
|
static CBsFixedAlloc _bsafxAlloc8(ROUND4(9*sizeof(TCHAR)+sizeof(CBsStringData)), 1024);
|
||
|
static CBsFixedAlloc _bsafxAlloc16(ROUND4(17*sizeof(TCHAR)+sizeof(CBsStringData)), 512);
|
||
|
static CBsFixedAlloc _bsafxAlloc32(ROUND4(33*sizeof(TCHAR)+sizeof(CBsStringData)), 256);
|
||
|
static CBsFixedAlloc _bsafxAlloc64(ROUND4(65*sizeof(TCHAR)+sizeof(CBsStringData)));
|
||
|
static CBsFixedAlloc _bsafxAlloc128(ROUND4(129*sizeof(TCHAR)+sizeof(CBsStringData)));
|
||
|
static CBsFixedAlloc _bsafxAlloc256(ROUND4(257*sizeof(TCHAR)+sizeof(CBsStringData)));
|
||
|
static CBsFixedAlloc _bsafxAlloc512(ROUND4(513*sizeof(TCHAR)+sizeof(CBsStringData)));
|
||
|
|
||
|
#endif //!_DEBUG
|
||
|
|
||
|
void CBsString::AllocBuffer(int nLen)
|
||
|
// always allocate one extra character for '\0' termination
|
||
|
// assumes [optimistically] that data length will equal allocation length
|
||
|
// Throws E_OUTOFMEMORY when out of memory
|
||
|
{
|
||
|
ASSERT(nLen >= 0);
|
||
|
ASSERT(nLen <= INT_MAX-1); // max size (enough room for 1 extra)
|
||
|
|
||
|
if (nLen == 0)
|
||
|
Init();
|
||
|
else
|
||
|
{
|
||
|
CBsStringData* pData;
|
||
|
#ifndef _DEBUG
|
||
|
if (nLen <= 8)
|
||
|
{
|
||
|
pData = (CBsStringData*)_bsafxAlloc8.Alloc();
|
||
|
pData->nAllocLength = 8;
|
||
|
}
|
||
|
else if (nLen <= 16)
|
||
|
{
|
||
|
pData = (CBsStringData*)_bsafxAlloc16.Alloc();
|
||
|
pData->nAllocLength = 16;
|
||
|
}
|
||
|
else if (nLen <= 32)
|
||
|
{
|
||
|
pData = (CBsStringData*)_bsafxAlloc32.Alloc();
|
||
|
pData->nAllocLength = 32;
|
||
|
}
|
||
|
else if (nLen <= 64)
|
||
|
{
|
||
|
pData = (CBsStringData*)_bsafxAlloc64.Alloc();
|
||
|
pData->nAllocLength = 64;
|
||
|
}
|
||
|
else if (nLen <= 128)
|
||
|
{
|
||
|
pData = (CBsStringData*)_bsafxAlloc128.Alloc();
|
||
|
pData->nAllocLength = 128;
|
||
|
}
|
||
|
else if (nLen <= 256)
|
||
|
{
|
||
|
pData = (CBsStringData*)_bsafxAlloc256.Alloc();
|
||
|
pData->nAllocLength = 256;
|
||
|
}
|
||
|
else if (nLen <= 512)
|
||
|
{
|
||
|
pData = (CBsStringData*)_bsafxAlloc512.Alloc();
|
||
|
pData->nAllocLength = 512;
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
pData = (CBsStringData*)
|
||
|
new BYTE[sizeof(CBsStringData) + (nLen+1)*sizeof(TCHAR)];
|
||
|
if ( pData == NULL ) // Prefix #118828
|
||
|
throw E_OUTOFMEMORY;
|
||
|
pData->nAllocLength = nLen;
|
||
|
}
|
||
|
pData->nRefs = 1;
|
||
|
pData->data()[nLen] = '\0';
|
||
|
pData->nDataLength = nLen;
|
||
|
m_pchData = pData->data();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FASTCALL CBsString::FreeData(CBsStringData* pData)
|
||
|
{
|
||
|
#ifndef _DEBUG
|
||
|
int nLen = pData->nAllocLength;
|
||
|
if (nLen == 8)
|
||
|
_bsafxAlloc8.Free(pData);
|
||
|
else if (nLen == 16)
|
||
|
_bsafxAlloc16.Free(pData);
|
||
|
else if (nLen == 32)
|
||
|
_bsafxAlloc32.Free(pData);
|
||
|
else if (nLen == 64)
|
||
|
_bsafxAlloc64.Free(pData);
|
||
|
else if (nLen == 128)
|
||
|
_bsafxAlloc128.Free(pData);
|
||
|
else if (nLen == 256)
|
||
|
_bsafxAlloc256.Free(pData);
|
||
|
else if (nLen == 512)
|
||
|
_bsafxAlloc512.Free(pData);
|
||
|
else
|
||
|
{
|
||
|
ASSERT(nLen > 512);
|
||
|
delete[] (BYTE*)pData;
|
||
|
}
|
||
|
#else
|
||
|
delete[] (BYTE*)pData;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void CBsString::Release()
|
||
|
{
|
||
|
if (GetData() != _bsafxDataNil)
|
||
|
{
|
||
|
ASSERT(GetData()->nRefs != 0);
|
||
|
if (InterlockedDecrement(&GetData()->nRefs) <= 0)
|
||
|
FreeData(GetData());
|
||
|
Init();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PASCAL CBsString::Release(CBsStringData* pData)
|
||
|
{
|
||
|
if (pData != _bsafxDataNil)
|
||
|
{
|
||
|
ASSERT(pData->nRefs != 0);
|
||
|
if (InterlockedDecrement(&pData->nRefs) <= 0)
|
||
|
FreeData(pData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CBsString::Empty()
|
||
|
{
|
||
|
if (GetData()->nDataLength == 0)
|
||
|
return;
|
||
|
if (GetData()->nRefs >= 0)
|
||
|
Release();
|
||
|
else
|
||
|
*this = &bsafxChNil;
|
||
|
ASSERT(GetData()->nDataLength == 0);
|
||
|
ASSERT(GetData()->nRefs < 0 || GetData()->nAllocLength == 0);
|
||
|
}
|
||
|
|
||
|
void CBsString::CopyBeforeWrite()
|
||
|
{
|
||
|
if (GetData()->nRefs > 1)
|
||
|
{
|
||
|
CBsStringData* pData = GetData();
|
||
|
Release();
|
||
|
AllocBuffer(pData->nDataLength);
|
||
|
memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(TCHAR));
|
||
|
}
|
||
|
ASSERT(GetData()->nRefs <= 1);
|
||
|
}
|
||
|
|
||
|
void CBsString::AllocBeforeWrite(int nLen)
|
||
|
{
|
||
|
if (GetData()->nRefs > 1 || nLen > GetData()->nAllocLength)
|
||
|
{
|
||
|
Release();
|
||
|
AllocBuffer(nLen);
|
||
|
}
|
||
|
ASSERT(GetData()->nRefs <= 1);
|
||
|
}
|
||
|
|
||
|
CBsString::~CBsString()
|
||
|
// free any attached data
|
||
|
{
|
||
|
if (GetData() != _bsafxDataNil)
|
||
|
{
|
||
|
if (InterlockedDecrement(&GetData()->nRefs) <= 0)
|
||
|
FreeData(GetData());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Helpers for the rest of the implementation
|
||
|
|
||
|
void CBsString::AllocCopy(CBsString& dest, int nCopyLen, int nCopyIndex,
|
||
|
int nExtraLen) const
|
||
|
{
|
||
|
// will clone the data attached to this string
|
||
|
// allocating 'nExtraLen' characters
|
||
|
// Places results in uninitialized string 'dest'
|
||
|
// Will copy the part or all of original data to start of new string
|
||
|
|
||
|
int nNewLen = nCopyLen + nExtraLen;
|
||
|
if (nNewLen == 0)
|
||
|
{
|
||
|
dest.Init();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dest.AllocBuffer(nNewLen);
|
||
|
memcpy(dest.m_pchData, m_pchData+nCopyIndex, nCopyLen*sizeof(TCHAR));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// More sophisticated construction
|
||
|
|
||
|
CBsString::CBsString(LPCTSTR lpsz)
|
||
|
{
|
||
|
Init();
|
||
|
int nLen = SafeStrlen(lpsz);
|
||
|
if (nLen != 0)
|
||
|
{
|
||
|
AllocBuffer(nLen);
|
||
|
memcpy(m_pchData, lpsz, nLen*sizeof(TCHAR));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// Special conversion constructors
|
||
|
|
||
|
#ifdef _UNICODE
|
||
|
CBsString::CBsString(LPCSTR lpsz)
|
||
|
{
|
||
|
Init();
|
||
|
int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0;
|
||
|
if (nSrcLen != 0)
|
||
|
{
|
||
|
AllocBuffer(nSrcLen);
|
||
|
_mbstowcsz(m_pchData, lpsz, nSrcLen+1);
|
||
|
ReleaseBuffer();
|
||
|
}
|
||
|
}
|
||
|
#else //_UNICODE
|
||
|
CBsString::CBsString(LPCWSTR lpsz)
|
||
|
{
|
||
|
Init();
|
||
|
int nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0;
|
||
|
if (nSrcLen != 0)
|
||
|
{
|
||
|
AllocBuffer(nSrcLen*2);
|
||
|
_wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1);
|
||
|
ReleaseBuffer();
|
||
|
}
|
||
|
}
|
||
|
#endif //!_UNICODE
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Assignment operators
|
||
|
// All assign a new value to the string
|
||
|
// (a) first see if the buffer is big enough
|
||
|
// (b) if enough room, copy on top of old buffer, set size and type
|
||
|
// (c) otherwise free old string data, and create a new one
|
||
|
//
|
||
|
// All routines return the new string (but as a 'const CBsString&' so that
|
||
|
// assigning it again will cause a copy, eg: s1 = s2 = "hi there".
|
||
|
//
|
||
|
|
||
|
void CBsString::AssignCopy(int nSrcLen, LPCTSTR lpszSrcData)
|
||
|
{
|
||
|
AllocBeforeWrite(nSrcLen);
|
||
|
memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR));
|
||
|
GetData()->nDataLength = nSrcLen;
|
||
|
m_pchData[nSrcLen] = '\0';
|
||
|
}
|
||
|
|
||
|
const CBsString& CBsString::operator=(const CBsString& stringSrc)
|
||
|
{
|
||
|
if (m_pchData != stringSrc.m_pchData)
|
||
|
{
|
||
|
if ((GetData()->nRefs < 0 && GetData() != _bsafxDataNil) ||
|
||
|
stringSrc.GetData()->nRefs < 0)
|
||
|
{
|
||
|
// actual copy necessary since one of the strings is locked
|
||
|
AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// can just copy references around
|
||
|
Release();
|
||
|
ASSERT(stringSrc.GetData() != _bsafxDataNil);
|
||
|
m_pchData = stringSrc.m_pchData;
|
||
|
InterlockedIncrement(&GetData()->nRefs);
|
||
|
}
|
||
|
}
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
const CBsString& CBsString::operator=(LPCTSTR lpsz)
|
||
|
{
|
||
|
ASSERT(lpsz == NULL || BsAfxIsValidString(lpsz));
|
||
|
AssignCopy(SafeStrlen(lpsz), lpsz);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// Special conversion assignment
|
||
|
|
||
|
#ifdef _UNICODE
|
||
|
const CBsString& CBsString::operator=(LPCSTR lpsz)
|
||
|
{
|
||
|
int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0;
|
||
|
AllocBeforeWrite(nSrcLen);
|
||
|
_mbstowcsz(m_pchData, lpsz, nSrcLen+1);
|
||
|
ReleaseBuffer();
|
||
|
return *this;
|
||
|
}
|
||
|
#else //!_UNICODE
|
||
|
const CBsString& CBsString::operator=(LPCWSTR lpsz)
|
||
|
{
|
||
|
int nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0;
|
||
|
AllocBeforeWrite(nSrcLen*2);
|
||
|
_wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1);
|
||
|
ReleaseBuffer();
|
||
|
return *this;
|
||
|
}
|
||
|
#endif //!_UNICODE
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// concatenation
|
||
|
|
||
|
// NOTE: "operator+" is done as friend functions for simplicity
|
||
|
// There are three variants:
|
||
|
// CBsString + CBsString
|
||
|
// and for ? = TCHAR, LPCTSTR
|
||
|
// CBsString + ?
|
||
|
// ? + CBsString
|
||
|
|
||
|
void CBsString::ConcatCopy(int nSrc1Len, LPCTSTR lpszSrc1Data,
|
||
|
int nSrc2Len, LPCTSTR lpszSrc2Data)
|
||
|
{
|
||
|
// -- master concatenation routine
|
||
|
// Concatenate two sources
|
||
|
// -- assume that 'this' is a new CBsString object
|
||
|
|
||
|
int nNewLen = nSrc1Len + nSrc2Len;
|
||
|
if (nNewLen != 0)
|
||
|
{
|
||
|
AllocBuffer(nNewLen);
|
||
|
memcpy(m_pchData, lpszSrc1Data, nSrc1Len*sizeof(TCHAR));
|
||
|
memcpy(m_pchData+nSrc1Len, lpszSrc2Data, nSrc2Len*sizeof(TCHAR));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CBsString BSAFXAPI operator+(const CBsString& string1, const CBsString& string2)
|
||
|
{
|
||
|
CBsString s;
|
||
|
s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData,
|
||
|
string2.GetData()->nDataLength, string2.m_pchData);
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
CBsString BSAFXAPI operator+(const CBsString& string, LPCTSTR lpsz)
|
||
|
{
|
||
|
ASSERT(lpsz == NULL || BsAfxIsValidString(lpsz));
|
||
|
CBsString s;
|
||
|
s.ConcatCopy(string.GetData()->nDataLength, string.m_pchData,
|
||
|
CBsString::SafeStrlen(lpsz), lpsz);
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
CBsString BSAFXAPI operator+(LPCTSTR lpsz, const CBsString& string)
|
||
|
{
|
||
|
ASSERT(lpsz == NULL || BsAfxIsValidString(lpsz));
|
||
|
CBsString s;
|
||
|
s.ConcatCopy(CBsString::SafeStrlen(lpsz), lpsz, string.GetData()->nDataLength,
|
||
|
string.m_pchData);
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// concatenate in place
|
||
|
|
||
|
void CBsString::ConcatInPlace(int nSrcLen, LPCTSTR lpszSrcData)
|
||
|
{
|
||
|
// -- the main routine for += operators
|
||
|
|
||
|
// concatenating an empty string is a no-op!
|
||
|
if (nSrcLen == 0)
|
||
|
return;
|
||
|
|
||
|
// if the buffer is too small, or we have a width mis-match, just
|
||
|
// allocate a new buffer (slow but sure)
|
||
|
if (GetData()->nRefs > 1 || GetData()->nDataLength + nSrcLen > GetData()->nAllocLength)
|
||
|
{
|
||
|
// we have to grow the buffer, use the ConcatCopy routine
|
||
|
CBsStringData* pOldData = GetData();
|
||
|
ConcatCopy(GetData()->nDataLength, m_pchData, nSrcLen, lpszSrcData);
|
||
|
ASSERT(pOldData != NULL);
|
||
|
CBsString::Release(pOldData);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// fast concatenation when buffer big enough
|
||
|
memcpy(m_pchData+GetData()->nDataLength, lpszSrcData, nSrcLen*sizeof(TCHAR));
|
||
|
GetData()->nDataLength += nSrcLen;
|
||
|
ASSERT(GetData()->nDataLength <= GetData()->nAllocLength);
|
||
|
m_pchData[GetData()->nDataLength] = '\0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const CBsString& CBsString::operator+=(LPCTSTR lpsz)
|
||
|
{
|
||
|
ASSERT(lpsz == NULL || BsAfxIsValidString(lpsz));
|
||
|
ConcatInPlace(SafeStrlen(lpsz), lpsz);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
const CBsString& CBsString::operator+=(TCHAR ch)
|
||
|
{
|
||
|
ConcatInPlace(1, &ch);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
const CBsString& CBsString::operator+=(const CBsString& string)
|
||
|
{
|
||
|
ConcatInPlace(string.GetData()->nDataLength, string.m_pchData);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// Advanced direct buffer access
|
||
|
|
||
|
LPTSTR CBsString::GetBuffer(int nMinBufLength)
|
||
|
{
|
||
|
ASSERT(nMinBufLength >= 0);
|
||
|
|
||
|
if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)
|
||
|
{
|
||
|
// we have to grow the buffer
|
||
|
CBsStringData* pOldData = GetData();
|
||
|
int nOldLen = GetData()->nDataLength; // AllocBuffer will tromp it
|
||
|
if (nMinBufLength < nOldLen)
|
||
|
nMinBufLength = nOldLen;
|
||
|
AllocBuffer(nMinBufLength);
|
||
|
memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR));
|
||
|
GetData()->nDataLength = nOldLen;
|
||
|
CBsString::Release(pOldData);
|
||
|
}
|
||
|
ASSERT(GetData()->nRefs <= 1);
|
||
|
|
||
|
// return a pointer to the character storage for this string
|
||
|
ASSERT(m_pchData != NULL);
|
||
|
return m_pchData;
|
||
|
}
|
||
|
|
||
|
void CBsString::ReleaseBuffer(int nNewLength)
|
||
|
{
|
||
|
CopyBeforeWrite(); // just in case GetBuffer was not called
|
||
|
|
||
|
if (nNewLength == -1)
|
||
|
nNewLength = lstrlen(m_pchData); // zero terminated
|
||
|
|
||
|
ASSERT(nNewLength <= GetData()->nAllocLength);
|
||
|
GetData()->nDataLength = nNewLength;
|
||
|
m_pchData[nNewLength] = '\0';
|
||
|
}
|
||
|
|
||
|
LPTSTR CBsString::GetBufferSetLength(int nNewLength)
|
||
|
{
|
||
|
ASSERT(nNewLength >= 0);
|
||
|
|
||
|
GetBuffer(nNewLength);
|
||
|
GetData()->nDataLength = nNewLength;
|
||
|
m_pchData[nNewLength] = '\0';
|
||
|
return m_pchData;
|
||
|
}
|
||
|
|
||
|
void CBsString::FreeExtra()
|
||
|
{
|
||
|
ASSERT(GetData()->nDataLength <= GetData()->nAllocLength);
|
||
|
if (GetData()->nDataLength != GetData()->nAllocLength)
|
||
|
{
|
||
|
CBsStringData* pOldData = GetData();
|
||
|
AllocBuffer(GetData()->nDataLength);
|
||
|
memcpy(m_pchData, pOldData->data(), pOldData->nDataLength*sizeof(TCHAR));
|
||
|
ASSERT(m_pchData[GetData()->nDataLength] == '\0');
|
||
|
CBsString::Release(pOldData);
|
||
|
}
|
||
|
ASSERT(GetData() != NULL);
|
||
|
}
|
||
|
|
||
|
LPTSTR CBsString::LockBuffer()
|
||
|
{
|
||
|
LPTSTR lpsz = GetBuffer(0);
|
||
|
GetData()->nRefs = -1;
|
||
|
return lpsz;
|
||
|
}
|
||
|
|
||
|
void CBsString::UnlockBuffer()
|
||
|
{
|
||
|
ASSERT(GetData()->nRefs == -1);
|
||
|
if (GetData() != _bsafxDataNil)
|
||
|
GetData()->nRefs = 1;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// Commonly used routines (rarely used routines in STREX.CPP)
|
||
|
|
||
|
int CBsString::Find(TCHAR ch) const
|
||
|
{
|
||
|
return Find(ch, 0);
|
||
|
}
|
||
|
|
||
|
int CBsString::Find(TCHAR ch, int nStart) const
|
||
|
{
|
||
|
int nLength = GetData()->nDataLength;
|
||
|
if (nStart >= nLength)
|
||
|
return -1;
|
||
|
|
||
|
// find first single character
|
||
|
LPTSTR lpsz = _tcschr(m_pchData + nStart, (_TUCHAR)ch);
|
||
|
|
||
|
// return -1 if not found and index otherwise
|
||
|
return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
|
||
|
}
|
||
|
|
||
|
int CBsString::FindOneOf(LPCTSTR lpszCharSet) const
|
||
|
{
|
||
|
ASSERT(BsAfxIsValidString(lpszCharSet));
|
||
|
LPTSTR lpsz = _tcspbrk(m_pchData, lpszCharSet);
|
||
|
return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
|
||
|
}
|
||
|
|
||
|
void CBsString::MakeUpper()
|
||
|
{
|
||
|
CopyBeforeWrite();
|
||
|
_tcsupr(m_pchData);
|
||
|
}
|
||
|
|
||
|
void CBsString::MakeLower()
|
||
|
{
|
||
|
CopyBeforeWrite();
|
||
|
_tcslwr(m_pchData);
|
||
|
}
|
||
|
|
||
|
void CBsString::MakeReverse()
|
||
|
{
|
||
|
CopyBeforeWrite();
|
||
|
_tcsrev(m_pchData);
|
||
|
}
|
||
|
|
||
|
void CBsString::SetAt(int nIndex, TCHAR ch)
|
||
|
{
|
||
|
ASSERT(nIndex >= 0);
|
||
|
ASSERT(nIndex < GetData()->nDataLength);
|
||
|
|
||
|
CopyBeforeWrite();
|
||
|
m_pchData[nIndex] = ch;
|
||
|
}
|
||
|
|
||
|
#ifndef _UNICODE
|
||
|
void CBsString::AnsiToOem()
|
||
|
{
|
||
|
CopyBeforeWrite();
|
||
|
::AnsiToOem(m_pchData, m_pchData);
|
||
|
}
|
||
|
void CBsString::OemToAnsi()
|
||
|
{
|
||
|
CopyBeforeWrite();
|
||
|
::OemToAnsi(m_pchData, m_pchData);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// CBsString conversion helpers (these use the current system locale)
|
||
|
|
||
|
int BSAFX_CDECL _wcstombsz(char* mbstr, const wchar_t* wcstr, size_t count)
|
||
|
{
|
||
|
if (count == 0 && mbstr != NULL)
|
||
|
return 0;
|
||
|
|
||
|
int result = ::WideCharToMultiByte(CP_ACP, 0, wcstr, -1,
|
||
|
mbstr, (INT)count, NULL, NULL);
|
||
|
ASSERT(mbstr == NULL || result <= (int)count);
|
||
|
if (result > 0)
|
||
|
mbstr[result-1] = 0;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
int BSAFX_CDECL _mbstowcsz(wchar_t* wcstr, const char* mbstr, size_t count)
|
||
|
{
|
||
|
if (count == 0 && wcstr != NULL)
|
||
|
return 0;
|
||
|
|
||
|
int result = ::MultiByteToWideChar(CP_ACP, 0, mbstr, -1,
|
||
|
wcstr, (INT)count);
|
||
|
ASSERT(wcstr == NULL || result <= (int)count);
|
||
|
if (result > 0)
|
||
|
wcstr[result-1] = 0;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
LPWSTR BSAFXAPI BsAfxA2WHelper(LPWSTR lpw, LPCSTR lpa, int nChars)
|
||
|
{
|
||
|
if (lpa == NULL)
|
||
|
return NULL;
|
||
|
ASSERT(lpw != NULL);
|
||
|
// verify that no illegal character present
|
||
|
// since lpw was allocated based on the size of lpa
|
||
|
// don't worry about the number of chars
|
||
|
lpw[0] = '\0';
|
||
|
MultiByteToWideChar(CP_ACP, 0, lpa, -1, lpw, nChars);
|
||
|
return lpw;
|
||
|
UNREFERENCED_PARAMETER( nChars );
|
||
|
}
|
||
|
|
||
|
LPSTR BSAFXAPI BsAfxW2AHelper(LPSTR lpa, LPCWSTR lpw, int nChars)
|
||
|
{
|
||
|
if (lpw == NULL)
|
||
|
return NULL;
|
||
|
ASSERT(lpa != NULL);
|
||
|
// verify that no illegal character present
|
||
|
// since lpa was allocated based on the size of lpw
|
||
|
// don't worry about the number of chars
|
||
|
lpa[0] = '\0';
|
||
|
WideCharToMultiByte(CP_ACP, 0, lpw, -1, lpa, nChars, NULL, NULL);
|
||
|
return lpa;
|
||
|
UNREFERENCED_PARAMETER( nChars );
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// the following is from strex.cpp
|
||
|
//
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// More sophisticated construction
|
||
|
|
||
|
CBsString::CBsString(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
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CBsString::CBsString(LPCTSTR lpch, int nLength)
|
||
|
{
|
||
|
Init();
|
||
|
if (nLength != 0)
|
||
|
{
|
||
|
ASSERT(BsAfxIsValidAddress(lpch, nLength, FALSE));
|
||
|
AllocBuffer(nLength);
|
||
|
memcpy(m_pchData, lpch, nLength*sizeof(TCHAR));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// Special conversion constructors
|
||
|
|
||
|
#ifdef _UNICODE
|
||
|
CBsString::CBsString(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
|
||
|
CBsString::CBsString(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 CBsString& CBsString::operator=(TCHAR ch)
|
||
|
{
|
||
|
AssignCopy(1, &ch);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// less common string expressions
|
||
|
|
||
|
CBsString BSAFXAPI operator+(const CBsString& string1, TCHAR ch)
|
||
|
{
|
||
|
CBsString s;
|
||
|
s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData, 1, &ch);
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
CBsString BSAFXAPI operator+(TCHAR ch, const CBsString& string)
|
||
|
{
|
||
|
CBsString s;
|
||
|
s.ConcatCopy(1, &ch, string.GetData()->nDataLength, string.m_pchData);
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Advanced manipulation
|
||
|
|
||
|
int CBsString::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 CBsString::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)
|
||
|
{
|
||
|
CBsStringData* pOldData = GetData();
|
||
|
LPTSTR pstr = m_pchData;
|
||
|
AllocBuffer(nNewLength);
|
||
|
memcpy(m_pchData, pstr, (pOldData->nDataLength+1)*sizeof(TCHAR));
|
||
|
CBsString::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 CBsString::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)
|
||
|
{
|
||
|
CBsStringData* pOldData = GetData();
|
||
|
LPTSTR pstr = m_pchData;
|
||
|
AllocBuffer(nNewLength);
|
||
|
memcpy(m_pchData, pstr, (pOldData->nDataLength+1)*sizeof(TCHAR));
|
||
|
CBsString::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 CBsString::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 CBsString::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)
|
||
|
{
|
||
|
CBsStringData* pOldData = GetData();
|
||
|
LPTSTR pstr = m_pchData;
|
||
|
AllocBuffer(nNewLength);
|
||
|
memcpy(m_pchData, pstr, pOldData->nDataLength*sizeof(TCHAR));
|
||
|
CBsString::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 - (int)(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 CBsString::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 = ( int )( pstrSource - pstrDest );
|
||
|
GetData()->nDataLength -= nCount;
|
||
|
|
||
|
return nCount;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Very simple sub-string extraction
|
||
|
|
||
|
CBsString CBsString::Mid(int nFirst) const
|
||
|
{
|
||
|
return Mid(nFirst, GetData()->nDataLength - nFirst);
|
||
|
}
|
||
|
|
||
|
CBsString CBsString::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;
|
||
|
|
||
|
CBsString dest;
|
||
|
AllocCopy(dest, nCount, nFirst, 0);
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
CBsString CBsString::Right(int nCount) const
|
||
|
{
|
||
|
if (nCount < 0)
|
||
|
nCount = 0;
|
||
|
if (nCount >= GetData()->nDataLength)
|
||
|
return *this;
|
||
|
|
||
|
CBsString dest;
|
||
|
AllocCopy(dest, nCount, GetData()->nDataLength-nCount, 0);
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
CBsString CBsString::Left(int nCount) const
|
||
|
{
|
||
|
if (nCount < 0)
|
||
|
nCount = 0;
|
||
|
if (nCount >= GetData()->nDataLength)
|
||
|
return *this;
|
||
|
|
||
|
CBsString dest;
|
||
|
AllocCopy(dest, nCount, 0, 0);
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
// strspn equivalent
|
||
|
CBsString CBsString::SpanIncluding(LPCTSTR lpszCharSet) const
|
||
|
{
|
||
|
ASSERT(BsAfxIsValidString(lpszCharSet));
|
||
|
return Left((INT)_tcsspn(m_pchData, lpszCharSet));
|
||
|
}
|
||
|
|
||
|
// strcspn equivalent
|
||
|
CBsString CBsString::SpanExcluding(LPCTSTR lpszCharSet) const
|
||
|
{
|
||
|
ASSERT(BsAfxIsValidString(lpszCharSet));
|
||
|
return Left((INT)_tcscspn(m_pchData, lpszCharSet));
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Finding
|
||
|
|
||
|
int CBsString::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 CBsString::Find(LPCTSTR lpszSub) const
|
||
|
{
|
||
|
return Find(lpszSub, 0);
|
||
|
}
|
||
|
|
||
|
int CBsString::Find(LPCTSTR lpszSub, int nStart) const
|
||
|
{
|
||
|
ASSERT(BsAfxIsValidString(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);
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CBsString formatting
|
||
|
|
||
|
#define TCHAR_ARG TCHAR
|
||
|
#define WCHAR_ARG WCHAR
|
||
|
#define CHAR_ARG char
|
||
|
|
||
|
#ifdef _X86_
|
||
|
#define DOUBLE_ARG _BSAFX_DOUBLE
|
||
|
#else
|
||
|
#define DOUBLE_ARG double
|
||
|
#endif
|
||
|
|
||
|
#define FORCE_ANSI 0x10000
|
||
|
#define FORCE_UNICODE 0x20000
|
||
|
#define FORCE_INT64 0x40000
|
||
|
|
||
|
void CBsString::FormatV(LPCTSTR lpszFormat, va_list argList)
|
||
|
{
|
||
|
ASSERT(BsAfxIsValidString(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 += (INT)_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 = (INT)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':
|
||
|
{
|
||
|
double f;
|
||
|
LPTSTR pszTemp;
|
||
|
|
||
|
// 312 == strlen("-1+(309 zeroes).")
|
||
|
// 309 zeroes == max precision of a double
|
||
|
// 6 == adjustment in case precision is not specified,
|
||
|
// which means that the precision defaults to 6
|
||
|
pszTemp = (LPTSTR)_alloca(max(nWidth, 312+nPrecision+6));
|
||
|
|
||
|
f = va_arg(argList, double);
|
||
|
_stprintf( pszTemp, _T( "%*.*f" ), nWidth, nPrecision+6, f );
|
||
|
nItemLen = _tcslen(pszTemp);
|
||
|
}
|
||
|
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);
|
||
|
INT i = _vstprintf(m_pchData, lpszFormat, argListSave);
|
||
|
ASSERT( i <= GetAllocLength() );
|
||
|
ReleaseBuffer();
|
||
|
|
||
|
va_end(argListSave);
|
||
|
}
|
||
|
|
||
|
// formatting (using wsprintf style formatting)
|
||
|
void BSAFX_CDECL CBsString::Format(LPCTSTR lpszFormat, ...)
|
||
|
{
|
||
|
ASSERT(BsAfxIsValidString(lpszFormat));
|
||
|
|
||
|
va_list argList;
|
||
|
va_start(argList, lpszFormat);
|
||
|
FormatV(lpszFormat, argList);
|
||
|
va_end(argList);
|
||
|
}
|
||
|
|
||
|
void CBsString::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 = (int)(lpszLast - m_pchData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CBsString::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 = (int)(lpszLast - m_pchData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBsString::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 = (int)(lpszLast - m_pchData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CBsString::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 - (int)(lpsz - m_pchData);
|
||
|
memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR));
|
||
|
GetData()->nDataLength = nDataLength;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CBsString::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 - (int)(lpsz - m_pchData);
|
||
|
memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR));
|
||
|
GetData()->nDataLength = nDataLength;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CBsString::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 - (int)(lpsz - m_pchData);
|
||
|
memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR));
|
||
|
GetData()->nDataLength = nDataLength;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// From validadd.cpp
|
||
|
//
|
||
|
|
||
|
BOOL BSAFXAPI BsAfxIsValidString(LPCWSTR lpsz, int nLength)
|
||
|
{
|
||
|
if (lpsz == NULL)
|
||
|
return FALSE;
|
||
|
return ::IsBadStringPtrW(lpsz, nLength) == 0;
|
||
|
}
|
||
|
|
||
|
BOOL BSAFXAPI BsAfxIsValidString(LPCSTR lpsz, int nLength)
|
||
|
{
|
||
|
if (lpsz == NULL)
|
||
|
return FALSE;
|
||
|
return ::IsBadStringPtrA(lpsz, nLength) == 0;
|
||
|
}
|
||
|
|
||
|
BOOL BSAFXAPI BsAfxIsValidAddress(const void* lp, UINT nBytes, BOOL bReadWrite)
|
||
|
{
|
||
|
// simple version using Win-32 APIs for pointer validation.
|
||
|
return (lp != NULL && !IsBadReadPtr(lp, nBytes) &&
|
||
|
(!bReadWrite || !IsBadWritePtr((LPVOID)lp, nBytes)));
|
||
|
}
|
||
|
///////////////////////////////////////////////////////////////////////////////
|