Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1214 lines
31 KiB

//========= Copyright (c) Valve Corporation, All rights reserved. =============
//
// Purpose: CUtlBinaryBlock and CUtlString implementation
//
//=============================================================================
#include "tier1/utlstring.h"
#include "tier1/utlvector.h"
#include "tier1/strtools.h"
#include <ctype.h>
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
static const int64 k_nMillion = 1000000;
//-----------------------------------------------------------------------------
// Purpose: Helper: Find s substring
//-----------------------------------------------------------------------------
static ptrdiff_t IndexOf(const char *pstrToSearch, const char *pstrTarget)
{
const char *pstrHit = V_strstr(pstrToSearch, pstrTarget);
if (pstrHit == NULL)
{
return -1; // Not found.
}
return (pstrHit - pstrToSearch);
}
//-----------------------------------------------------------------------------
// Purpose: Helper: kill all whitespace.
//-----------------------------------------------------------------------------
static size_t RemoveWhitespace(char *pszString)
{
if (pszString == NULL)
return 0;
char *pstrDest = pszString;
size_t cRemoved = 0;
for (char *pstrWalker = pszString; *pstrWalker != 0; pstrWalker++)
{
if (!V_isspace((unsigned char)*pstrWalker))
{
*pstrDest = *pstrWalker;
pstrDest++;
}
else
cRemoved += 1;
}
*pstrDest = 0;
return cRemoved;
}
int V_vscprintf(const char *format, va_list params)
{
#ifdef _WIN32
return _vscprintf(format, params);
#else
return vsnprintf(NULL, 0, format, params);
#endif
}
//-----------------------------------------------------------------------------
// Base class, containing simple memory management
//-----------------------------------------------------------------------------
CUtlBinaryBlock::CUtlBinaryBlock( int growSize, int initSize )
{
MEM_ALLOC_CREDIT();
m_Memory.Init( growSize, initSize );
m_nActualLength = 0;
}
CUtlBinaryBlock::CUtlBinaryBlock( void* pMemory, int nSizeInBytes, int nInitialLength ) : m_Memory( (unsigned char*)pMemory, nSizeInBytes )
{
m_nActualLength = nInitialLength;
}
CUtlBinaryBlock::CUtlBinaryBlock( const void* pMemory, int nSizeInBytes ) : m_Memory( (const unsigned char*)pMemory, nSizeInBytes )
{
m_nActualLength = nSizeInBytes;
}
CUtlBinaryBlock::CUtlBinaryBlock( const CUtlBinaryBlock& src )
{
Set( src.Get(), src.Length() );
}
void CUtlBinaryBlock::Get( void *pValue, int nLen ) const
{
Assert( nLen > 0 );
if ( m_nActualLength < nLen )
{
nLen = m_nActualLength;
}
if ( nLen > 0 )
{
memcpy( pValue, m_Memory.Base(), nLen );
}
}
void CUtlBinaryBlock::SetLength( int nLength )
{
MEM_ALLOC_CREDIT();
Assert( !m_Memory.IsReadOnly() );
m_nActualLength = nLength;
if ( nLength > m_Memory.NumAllocated() )
{
int nOverFlow = nLength - m_Memory.NumAllocated();
m_Memory.Grow( nOverFlow );
// If the reallocation failed, clamp length
if ( nLength > m_Memory.NumAllocated() )
{
m_nActualLength = m_Memory.NumAllocated();
}
}
#ifdef _DEBUG
if ( m_Memory.NumAllocated() > m_nActualLength )
{
memset( ( ( char * )m_Memory.Base() ) + m_nActualLength, 0xEB, m_Memory.NumAllocated() - m_nActualLength );
}
#endif
}
void CUtlBinaryBlock::Set( const void *pValue, int nLen )
{
Assert( !m_Memory.IsReadOnly() );
if ( !pValue )
{
nLen = 0;
}
SetLength( nLen );
if ( m_nActualLength )
{
if ( ( ( const char * )m_Memory.Base() ) >= ( ( const char * )pValue ) + nLen ||
( ( const char * )m_Memory.Base() ) + m_nActualLength <= ( ( const char * )pValue ) )
{
memcpy( m_Memory.Base(), pValue, m_nActualLength );
}
else
{
memmove( m_Memory.Base(), pValue, m_nActualLength );
}
}
}
CUtlBinaryBlock &CUtlBinaryBlock::operator=( const CUtlBinaryBlock &src )
{
Assert( !m_Memory.IsReadOnly() );
Set( src.Get(), src.Length() );
return *this;
}
bool CUtlBinaryBlock::operator==( const CUtlBinaryBlock &src ) const
{
if ( src.Length() != Length() )
return false;
return !memcmp( src.Get(), Get(), Length() );
}
//-----------------------------------------------------------------------------
// Simple string class.
//-----------------------------------------------------------------------------
CUtlString::CUtlString()
{
}
CUtlString::CUtlString( const char *pString )
{
Set( pString );
}
CUtlString::CUtlString( const CUtlString& string )
{
Set( string.Get() );
}
// Attaches the string to external memory. Useful for avoiding a copy
CUtlString::CUtlString( void* pMemory, int nSizeInBytes, int nInitialLength ) : m_Storage( pMemory, nSizeInBytes, nInitialLength )
{
}
CUtlString::CUtlString( const void* pMemory, int nSizeInBytes ) : m_Storage( pMemory, nSizeInBytes )
{
}
//-----------------------------------------------------------------------------
// Purpose: Set directly and don't look for a null terminator in pValue.
//-----------------------------------------------------------------------------
void CUtlString::SetDirect( const char *pValue, int nChars )
{
if ( nChars > 0 )
{
m_Storage.SetLength( nChars+1 );
m_Storage.Set( pValue, nChars+1 );
m_Storage[nChars] = 0;
}
else
{
m_Storage.SetLength( 0 );
}
}
void CUtlString::Set( const char *pValue )
{
Assert( !m_Storage.IsReadOnly() );
int nLen = pValue ? Q_strlen(pValue) + 1 : 0;
m_Storage.Set( pValue, nLen );
}
// Returns strlen
int CUtlString::Length() const
{
return m_Storage.Length() ? m_Storage.Length() - 1 : 0;
}
// Sets the length (used to serialize into the buffer )
void CUtlString::SetLength( int nLen )
{
Assert( !m_Storage.IsReadOnly() );
// Add 1 to account for the NULL
m_Storage.SetLength( nLen > 0 ? nLen + 1 : 0 );
}
const char *CUtlString::Get( ) const
{
if ( m_Storage.Length() == 0 )
{
return "";
}
return reinterpret_cast< const char* >( m_Storage.Get() );
}
char *CUtlString::Get()
{
Assert( !m_Storage.IsReadOnly() );
if ( m_Storage.Length() == 0 )
{
// In general, we optimise away small mallocs for empty strings
// but if you ask for the non-const bytes, they must be writable
// so we can't return "" here, like we do for the const version - jd
m_Storage.SetLength( 1 );
m_Storage[ 0 ] = '\0';
}
return reinterpret_cast< char* >( m_Storage.Get() );
}
char *CUtlString::GetForModify()
{
return Get();
}
void CUtlString::Purge()
{
m_Storage.Purge();
}
void CUtlString::ToUpper()
{
for (int nLength = Length() - 1; nLength >= 0; nLength--)
{
m_Storage[nLength] = toupper(m_Storage[nLength]);
}
}
void CUtlString::ToLower()
{
for( int nLength = Length() - 1; nLength >= 0; nLength-- )
{
m_Storage[ nLength ] = tolower( m_Storage[ nLength ] );
}
}
CUtlString &CUtlString::operator=( const CUtlString &src )
{
Assert( !m_Storage.IsReadOnly() );
m_Storage = src.m_Storage;
return *this;
}
CUtlString &CUtlString::operator=( const char *src )
{
Assert( !m_Storage.IsReadOnly() );
Set( src );
return *this;
}
bool CUtlString::operator==( const CUtlString &src ) const
{
return m_Storage == src.m_Storage;
}
bool CUtlString::operator==( const char *src ) const
{
return ( strcmp( Get(), src ) == 0 );
}
CUtlString &CUtlString::operator+=( const CUtlString &rhs )
{
Assert( !m_Storage.IsReadOnly() );
const int lhsLength( Length() );
const int rhsLength( rhs.Length() );
const int requestedLength( lhsLength + rhsLength );
SetLength( requestedLength );
const int allocatedLength( Length() );
const int copyLength( allocatedLength - lhsLength < rhsLength ? allocatedLength - lhsLength : rhsLength );
memcpy( Get() + lhsLength, rhs.Get(), copyLength );
m_Storage[ allocatedLength ] = '\0';
return *this;
}
CUtlString &CUtlString::operator+=( const char *rhs )
{
Assert( !m_Storage.IsReadOnly() );
const int lhsLength( Length() );
const int rhsLength( Q_strlen( rhs ) );
const int requestedLength( lhsLength + rhsLength );
SetLength( requestedLength );
const int allocatedLength( Length() );
const int copyLength( allocatedLength - lhsLength < rhsLength ? allocatedLength - lhsLength : rhsLength );
memcpy( Get() + lhsLength, rhs, copyLength );
m_Storage[ allocatedLength ] = '\0';
return *this;
}
CUtlString &CUtlString::operator+=( char c )
{
Assert( !m_Storage.IsReadOnly() );
int nLength = Length();
SetLength( nLength + 1 );
m_Storage[ nLength ] = c;
m_Storage[ nLength+1 ] = '\0';
return *this;
}
CUtlString &CUtlString::operator+=( int rhs )
{
Assert( !m_Storage.IsReadOnly() );
Assert( sizeof( rhs ) == 4 );
char tmpBuf[ 12 ]; // Sufficient for a signed 32 bit integer [ -2147483648 to +2147483647 ]
Q_snprintf( tmpBuf, sizeof( tmpBuf ), "%d", rhs );
tmpBuf[ sizeof( tmpBuf ) - 1 ] = '\0';
return operator+=( tmpBuf );
}
CUtlString &CUtlString::operator+=( double rhs )
{
Assert( !m_Storage.IsReadOnly() );
char tmpBuf[ 256 ]; // How big can doubles be??? Dunno.
Q_snprintf( tmpBuf, sizeof( tmpBuf ), "%lg", rhs );
tmpBuf[ sizeof( tmpBuf ) - 1 ] = '\0';
return operator+=( tmpBuf );
}
bool CUtlString::MatchesPattern( const CUtlString &Pattern, int nFlags )
{
const char *pszSource = String();
const char *pszPattern = Pattern.String();
return V_StringMatchesPattern( pszSource, pszPattern, nFlags );
}
int CUtlString::Format( const char *pFormat, ... )
{
va_list marker;
va_start( marker, pFormat );
int len = FormatV( pFormat, marker );
va_end( marker );
return len;
}
//--------------------------------------------------------------------------------------------------
// This can be called from functions that take varargs.
//--------------------------------------------------------------------------------------------------
int CUtlString::FormatV( const char *pFormat, va_list marker )
{
char tmpBuf[ 4096 ]; //< Nice big 4k buffer, as much memory as my first computer had, a Radio Shack Color Computer
//va_start( marker, pFormat );
int len = V_vsprintf_safe( tmpBuf, pFormat, marker );
//va_end( marker );
Set( tmpBuf );
return len;
}
//-----------------------------------------------------------------------------
// Strips the trailing slash
//-----------------------------------------------------------------------------
void CUtlString::StripTrailingSlash()
{
if ( IsEmpty() )
return;
int nLastChar = Length() - 1;
char c = m_Storage[ nLastChar ];
if ( c == '\\' || c == '/' )
{
m_Storage[ nLastChar ] = 0;
m_Storage.SetLength( m_Storage.Length() - 1 );
}
}
CUtlString CUtlString::Slice( int32 nStart, int32 nEnd )
{
if ( nStart < 0 )
nStart = Length() - (-nStart % Length());
else if ( nStart >= Length() )
nStart = Length();
if ( nEnd == INT32_MAX )
nEnd = Length();
else if ( nEnd < 0 )
nEnd = Length() - (-nEnd % Length());
else if ( nEnd >= Length() )
nEnd = Length();
if ( nStart >= nEnd )
return CUtlString( "" );
const char *pIn = String();
CUtlString ret;
ret.m_Storage.SetLength( nEnd - nStart + 1 );
char *pOut = (char*)ret.m_Storage.Get();
memcpy( ret.m_Storage.Get(), &pIn[nStart], nEnd - nStart );
pOut[nEnd - nStart] = 0;
return ret;
}
// Grab a substring starting from the left or the right side.
CUtlString CUtlString::Left( int32 nChars )
{
return Slice( 0, nChars );
}
CUtlString CUtlString::Right( int32 nChars )
{
return Slice( -nChars );
}
// Get a string with the specified substring removed
CUtlString CUtlString::Remove( char const *pTextToRemove, bool bCaseSensitive ) const
{
int nTextToRemoveLength = pTextToRemove ? V_strlen( pTextToRemove ) : 0;
CUtlString outputString;
const char *pSrc = Get();
if ( pSrc )
{
while ( *pSrc )
{
char const *pNextOccurrence = bCaseSensitive ? V_strstr( pSrc, pTextToRemove ) : V_stristr( pSrc, pTextToRemove );
if ( !pNextOccurrence )
{
// append remaining string
outputString += pSrc;
break;
}
int nNumCharsToCopy = pNextOccurrence - pSrc;
if ( nNumCharsToCopy )
{
// append up to the undesired substring
CUtlString temp = pSrc;
temp = temp.Left( nNumCharsToCopy );
outputString += temp;
}
// skip past undesired substring
pSrc = pNextOccurrence + nTextToRemoveLength;
}
}
return outputString;
}
CUtlString CUtlString::Replace( char const *pchFrom, const char *pchTo, bool bCaseSensitive /*= false*/ ) const
{
if ( !pchTo )
{
return Remove( pchFrom, bCaseSensitive );
}
int nTextToReplaceLength = pchFrom ? V_strlen( pchFrom ) : 0;
CUtlString outputString;
const char *pSrc = Get();
if ( pSrc )
{
while ( *pSrc )
{
char const *pNextOccurrence = bCaseSensitive ? V_strstr( pSrc, pchFrom ) : V_stristr( pSrc, pchFrom );
if ( !pNextOccurrence )
{
// append remaining string
outputString += pSrc;
break;
}
int nNumCharsToCopy = pNextOccurrence - pSrc;
if ( nNumCharsToCopy )
{
// append up to the undesired substring
CUtlString temp = pSrc;
temp = temp.Left( nNumCharsToCopy );
outputString += temp;
}
// Append the replacement
outputString += pchTo;
// skip past undesired substring
pSrc = pNextOccurrence + nTextToReplaceLength;
}
}
return outputString;
}
CUtlString CUtlString::Replace( char cFrom, char cTo )
{
CUtlString ret = *this;
int len = ret.Length();
for ( int i=0; i < len; i++ )
{
if ( ret.m_Storage[i] == cFrom )
ret.m_Storage[i] = cTo;
}
return ret;
}
void CUtlString::RemoveDotSlashes(char separator)
{
V_RemoveDotSlashes(Get(), separator);
}
void CUtlString::Swap( CUtlString &src )
{
CUtlString tmp = src;
src = *this;
*this = tmp;
}
CUtlString CUtlString::AbsPath( const char *pStartingDir ) const
{
char szNew[MAX_PATH];
V_MakeAbsolutePath( szNew, sizeof( szNew ), this->String(), pStartingDir );
return CUtlString( szNew );
}
CUtlString CUtlString::UnqualifiedFilename() const
{
const char *pFilename = V_UnqualifiedFileName( this->String() );
return CUtlString( pFilename );
}
CUtlString CUtlString::DirName()
{
CUtlString ret( this->String() );
V_StripLastDir( (char*)ret.m_Storage.Get(), ret.m_Storage.Length() );
V_StripTrailingSlash( (char*)ret.m_Storage.Get() );
return ret;
}
CUtlString CUtlString::StripExtension() const
{
char szTemp[MAX_FILEPATH];
V_StripExtension( String(), szTemp, sizeof( szTemp ) );
return CUtlString( szTemp );
}
CUtlString CUtlString::StripFilename() const
{
const char *pFilename = V_UnqualifiedFileName( Get() ); // NOTE: returns 'Get()' on failure, never NULL
int nCharsToCopy = pFilename - Get();
CUtlString result;
result.SetDirect( Get(), nCharsToCopy );
result.StripTrailingSlash();
return result;
}
CUtlString CUtlString::GetBaseFilename() const
{
char szTemp[MAX_FILEPATH];
V_FileBase( String(), szTemp, sizeof( szTemp ) );
return CUtlString( szTemp );
}
CUtlString CUtlString::GetExtension() const
{
char szTemp[MAX_FILEPATH];
V_ExtractFileExtension( String(), szTemp, sizeof( szTemp ) );
return CUtlString( szTemp );
}
CUtlString CUtlString::PathJoin( const char *pStr1, const char *pStr2 )
{
char szPath[MAX_PATH];
V_ComposeFileName( pStr1, pStr2, szPath, sizeof( szPath ) );
return CUtlString( szPath );
}
//-----------------------------------------------------------------------------
// Purpose: concatenate the provided string to our current content
//-----------------------------------------------------------------------------
void CUtlString::Append( const char *pchAddition )
{
(*this) += pchAddition;
}
void CUtlString::Append(const char *pchAddition, int nMaxChars)
{
const int nLen = V_strlen(pchAddition);
if (nMaxChars < 0 || nLen <= nMaxChars)
{
Append(pchAddition);
}
else
{
char* pchAdditionDup = V_strdup(pchAddition);
pchAdditionDup[nMaxChars] = 0;
Append(pchAdditionDup);
delete[] pchAdditionDup;
}
}
//--------------------------------------------------------------------------------------------------
// Trim
//--------------------------------------------------------------------------------------------------
void CUtlString::TrimLeft( const char *szTargets )
{
int i;
if ( IsEmpty() )
{
return;
}
char* pSrc = Get();
for ( i = 0; pSrc[ i ] != 0; i++ )
{
bool bWhitespace = false;
for ( int j = 0; szTargets[ j ] != 0; j++ )
{
if ( pSrc[ i ] == szTargets[ j ] )
{
bWhitespace = true;
break;
}
}
if ( !bWhitespace )
{
break;
}
}
// We have some whitespace to remove
if ( i > 0 )
{
memmove( pSrc, &pSrc[ i ], Length() - i );
SetLength( Length() - i );
m_Storage[ Length() ] = '\0';
}
}
void CUtlString::TrimRight( const char *szTargets )
{
const int nLastCharIndex = Length() - 1;
int i;
char* pSrc = Get();
for ( i = nLastCharIndex; i >= 0; i-- )
{
bool bWhitespace = false;
for ( int j = 0; szTargets[ j ] != 0; j++ )
{
if ( pSrc[ i ] == szTargets[ j ] )
{
bWhitespace = true;
break;
}
}
if ( !bWhitespace )
{
break;
}
}
// We have some whitespace to remove
if ( i < nLastCharIndex )
{
pSrc[ i + 1 ] = 0;
SetLength( i + 1 );
}
}
void CUtlString::Trim( const char *szTargets )
{
TrimLeft( szTargets );
TrimRight( szTargets );
}
//-----------------------------------------------------------------------------
// Purpose: spill routine for making sure our buffer is big enough for an
// incoming string set/modify.
//-----------------------------------------------------------------------------
char *CUtlStringBuilder::InternalPrepareBuffer(size_t nChars, bool bCopyOld, size_t nMinCapacity)
{
Assert(nMinCapacity > Capacity());
Assert(nMinCapacity >= nChars);
// Don't use this class if you want a single 2GB+ string.
static const size_t k_nMaxStringSize = 0x7FFFFFFFu;
Assert(nMinCapacity <= k_nMaxStringSize);
if (nMinCapacity > k_nMaxStringSize)
{
SetError();
return NULL;
}
bool bWasHeap = m_data.IsHeap();
// add this to whatever we are going to grow so we don't start out too slow
char *pszString = NULL;
if (nMinCapacity > MAX_STACK_STRLEN)
{
// Allocate 1.5 times what is requested, plus a small initial ramp
// value so we don't spend too much time re-allocating tiny buffers.
// A good allocator will prevent this anyways, but this makes it safer.
// We cap it at +1 million to not get crazy. Code actually avoides
// computing power of two numbers since allocations almost always
// have header/bookkeeping overhead. Don't do the dynamic sizing
// if the user asked for a specific capacity.
static const int k_nInitialMinRamp = 32;
size_t nNewSize;
if (nMinCapacity > nChars)
nNewSize = nMinCapacity;
else
nNewSize = nChars + Min<size_t>((nChars >> 1) + k_nInitialMinRamp, k_nMillion);
char *pszOld = m_data.Access();
size_t nLenOld = m_data.Length();
// order of operations is very important per comment
// above. Make sure we copy it before changing m_data
// in any way
if (bWasHeap && bCopyOld)
{
// maybe we'll get lucky and get the same buffer back.
pszString = (char*)realloc(pszOld, nNewSize + 1);
if (!pszString)
{
SetError();
return NULL;
}
}
else // Either it's already on the stack; or we don't need to copy
{
// if the current pointer is on the heap, we aren't doing a copy
// (or we would have used the previous realloc code. So
// if we aren't doing a copy, don't use realloc since it will
// copy the data if it needs to make a new allocation.
if (bWasHeap)
free(pszOld);
pszString = (char*)malloc(nNewSize + 1);
if (!pszString)
{
SetError();
return NULL;
}
// still need to do the copy if we are going from small buffer to large
if (bCopyOld)
memcpy(pszString, pszOld, nLenOld); // null will be added at end of func.
}
// just in case the user grabs .Access() and scribbles over the terminator at
// 'length', make sure they don't run off the rails as long as they obey Capacity.
// We don't offer this protection for the 'on stack' string.
pszString[nNewSize] = '\0';
m_data.Heap.m_pchString = pszString;
m_data.Heap.m_nCapacity = (uint32)nNewSize; // capacity is the max #chars, not including the null.
m_data.Heap.m_nLength = (uint32)nChars;
m_data.Heap.sentinel = STRING_TYPE_SENTINEL;
}
else
{
// Rare case. Only happens if someone did a SetPtr with a length
// less than MAX_STACK_STRLEN, or maybe a .Replace() shrunk the
// length down.
pszString = m_data.Stack.m_szString;
m_data.Stack.SetBytesLeft(MAX_STACK_STRLEN - (uint8)nChars);
if (bWasHeap)
{
char *pszOldString = m_data.Heap.m_pchString;
if (bCopyOld)
memcpy(pszString, pszOldString, nChars); // null will be added at end of func.
free(pszOldString);
}
}
pszString[nChars] = '\0';
return pszString;
}
//-----------------------------------------------------------------------------
// Purpose: replace all occurrences of one string with another
// replacement string may be NULL or "" to remove target string
//-----------------------------------------------------------------------------
size_t CUtlStringBuilder::Replace(const char *pstrTarget, const char *pstrReplacement)
{
return ReplaceInternal(pstrTarget, pstrReplacement, (const char *(*)(const char *, const char *))_V_strstr);
}
//-----------------------------------------------------------------------------
// Purpose: replace all occurrences of one string with another
// replacement string may be NULL or "" to remove target string
//-----------------------------------------------------------------------------
size_t CUtlStringBuilder::ReplaceFastCaseless(const char *pstrTarget, const char *pstrReplacement)
{
return ReplaceInternal(pstrTarget, pstrReplacement, V_stristr_fast);
}
//-----------------------------------------------------------------------------
// Purpose: replace all occurrences of one string with another
// replacement string may be NULL or "" to remove target string
//-----------------------------------------------------------------------------
size_t CUtlStringBuilder::ReplaceInternal(const char *pstrTarget, const char *pstrReplacement, const char *pfnCompare(const char*, const char*))
{
if (HasError())
return 0;
if (pstrReplacement == NULL)
pstrReplacement = "";
size_t nTargetLength = V_strlen(pstrTarget);
size_t nReplacementLength = V_strlen(pstrReplacement);
CUtlVector<const char *> vecMatches;
vecMatches.EnsureCapacity(8);
if (!IsEmpty() && pstrTarget && *pstrTarget)
{
char *pszString = Access();
// walk the string counting hits
const char *pstrHit = pszString;
for (pstrHit = pfnCompare(pstrHit, pstrTarget); pstrHit != NULL && *pstrHit != 0; /* inside */)
{
vecMatches.AddToTail(pstrHit);
// look for the next target and keep looping
pstrHit = pfnCompare(pstrHit + nTargetLength, pstrTarget);
}
// if we didn't miss, get to work
if (vecMatches.Count() > 0)
{
// reallocate only once; how big will we need?
size_t nOldLength = Length();
size_t nNewLength = nOldLength + (vecMatches.Count() * (int)(nReplacementLength - nTargetLength));
if (nNewLength == 0)
{
// shortcut simple case, even if rare
m_data.Clear();
}
else if (nNewLength > nOldLength)
{
// New string will be bigger than the old, but don't re-alloc unless
// it is also larger than capacity. If it fits in capacity, we will
// be adjusting the string 'in place'. The replacement string is larger
// than the target string, so if we copied front to back we would screw up
// the existing data in the 'in place' case.
char *pstrNew;
if (nNewLength > Capacity())
{
pstrNew = (char*)malloc(nNewLength + 1);
if (!pstrNew)
{
SetError();
return 0;
}
}
else
{
pstrNew = PrepareBuffer(nNewLength);
Assert(pstrNew == pszString);
}
const char *pstrPreviousHit = pszString + nOldLength; // end of original string
char *pstrDestination = pstrNew + nNewLength; // end of target
*pstrDestination = '\0';
// Go backwards as noted above.
FOR_EACH_VEC_BACK(vecMatches, i)
{
pstrHit = vecMatches[i];
size_t nRemainder = pstrPreviousHit - (pstrHit + nTargetLength);
// copy the bit after the match, back up the destination and move forward from the hit
memmove(pstrDestination - nRemainder, pstrPreviousHit - nRemainder, nRemainder);
pstrDestination -= (nRemainder + nReplacementLength);
// push the replacement string in
memcpy(pstrDestination, pstrReplacement, nReplacementLength);
pstrPreviousHit = pstrHit;
}
// copy trailing stuff
size_t nRemainder = pstrPreviousHit - pszString;
pstrDestination -= nRemainder;
if (pstrDestination != pszString)
{
memmove(pstrDestination, pszString, nRemainder);
}
Assert(pstrNew == pstrDestination);
// Need to set the pointer if we did were larger than capacity.
if (pstrNew != pszString)
SetPtr(pstrNew, nNewLength);
}
else // new is shorter than or same length as old, move in place
{
char *pstrNew = Access();
char *pstrPreviousHit = pstrNew;
char *pstrDestination = pstrNew;
FOR_EACH_VEC(vecMatches, i)
{
pstrHit = vecMatches[i];
if (pstrDestination != pstrPreviousHit)
{
// memmove very important as it is ok with overlaps.
memmove(pstrDestination, pstrPreviousHit, pstrHit - pstrPreviousHit);
}
pstrDestination += (pstrHit - pstrPreviousHit);
memcpy(pstrDestination, pstrReplacement, nReplacementLength);
pstrDestination += nReplacementLength;
pstrPreviousHit = const_cast<char*>(pstrHit)+nTargetLength;
}
// copy trailing stuff
if (pstrDestination != pstrPreviousHit)
{
// memmove very important as it is ok with overlaps.
size_t nRemainder = (pstrNew + nOldLength) - pstrPreviousHit;
memmove(pstrDestination, pstrPreviousHit, nRemainder);
}
Verify(PrepareBuffer(nNewLength) == pstrNew);
}
}
}
return vecMatches.Count();
}
//-----------------------------------------------------------------------------
// Purpose: Indicates if the target string exists in this instance.
// The index is negative if the target string is not found, otherwise it is the index in the string.
//-----------------------------------------------------------------------------
ptrdiff_t CUtlStringBuilder::IndexOf(const char *pstrTarget) const
{
return ::IndexOf(String(), pstrTarget);
}
//-----------------------------------------------------------------------------
// Purpose:
// remove whitespace -- anything that is isspace() -- from the string
//-----------------------------------------------------------------------------
size_t CUtlStringBuilder::RemoveWhitespace()
{
if (HasError())
return 0;
char *pstrDest = m_data.Access();
size_t cRemoved = ::RemoveWhitespace(pstrDest);
size_t nNewLength = m_data.Length() - cRemoved;
if (cRemoved)
m_data.SetLength(nNewLength);
Assert(pstrDest[nNewLength] == '\0'); // SetLength should have set this
return cRemoved;
}
//-----------------------------------------------------------------------------
// Purpose: Allows setting the size to anything under the current
// capacity. Typically should not be used unless there was a specific
// reason to scribble on the string. Will not touch the string contents,
// but will append a NULL. Returns true if the length was changed.
//-----------------------------------------------------------------------------
bool CUtlStringBuilder::SetLength(size_t nLen)
{
return m_data.SetLength(nLen) != NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Convert to heap string if needed, and give it away.
//-----------------------------------------------------------------------------
char *CUtlStringBuilder::TakeOwnership(size_t *pnLen, size_t *pnCapacity)
{
size_t nLen = 0;
size_t nCapacity = 0;
char *psz = m_data.TakeOwnership(nLen, nCapacity);
if (pnLen)
*pnLen = nLen;
if (pnCapacity)
*pnCapacity = nCapacity;
return psz;
}
//-----------------------------------------------------------------------------
// Purpose:
// trim whitespace from front and back of string
//-----------------------------------------------------------------------------
size_t CUtlStringBuilder::TrimWhitespace()
{
if (HasError())
return 0;
char *pchString = m_data.Access();
int cChars = V_StrTrim(pchString);
if (cChars)
m_data.SetLength(cChars);
return cChars;
}
//-----------------------------------------------------------------------------
// Purpose: adjust length and add null terminator, within capacity bounds
//-----------------------------------------------------------------------------
char *CUtlStringBuilder::Data::SetLength(size_t nChars)
{
// heap/stack must be set correctly, and will not
// be changed by this routine.
if (IsHeap())
{
if (!Heap.m_pchString || nChars > Heap.m_nCapacity)
return NULL;
Heap.m_nLength = (uint32)nChars;
Heap.m_pchString[nChars] = '\0';
return Heap.m_pchString;
}
if (nChars > MAX_STACK_STRLEN)
return NULL;
Stack.m_szString[nChars] = '\0';
Stack.SetBytesLeft(MAX_STACK_STRLEN - (uint8)nChars);
return Stack.m_szString;
}
//-----------------------------------------------------------------------------
// Purpose: Allows setting the raw pointer and taking ownership
//-----------------------------------------------------------------------------
void CUtlStringBuilder::Data::SetPtr(char *pchString, size_t nLength)
{
// We don't care about the error state since we are totally replacing
// the string.
// ok, length may be small enough to fit in our short buffer
// but we've already got a dynamically allocated string, so let
// it be in the heap buffer anyways.
Heap.m_pchString = pchString;
Heap.m_nCapacity = (uint32)nLength;
Heap.m_nLength = (uint32)nLength;
Heap.sentinel = STRING_TYPE_SENTINEL;
// their buffer must have room for the null
Heap.m_pchString[nLength] = '\0';
}
//-----------------------------------------------------------------------------
// Purpose: Enable the error state, moving the string to the heap if
// it isn't there.
//-----------------------------------------------------------------------------
void CUtlStringBuilder::Data::SetError(bool bEnableAssert)
{
if (HasError())
return;
// This is not meant to be used as a status bit. Setting the error state should
// mean something very unexpected happened that you would want a call stack for.
// That is why this asserts unconditionally when the state is being flipped.
if (bEnableAssert)
AssertMsg(false, "Error State on string being set.");
MoveToHeap();
Heap.sentinel = (STRING_TYPE_SENTINEL | STRING_TYPE_ERROR);
}
//-----------------------------------------------------------------------------
// Purpose: Set string to empty state
//-----------------------------------------------------------------------------
void CUtlStringBuilder::Data::ClearError()
{
if (HasError())
{
Heap.sentinel = STRING_TYPE_SENTINEL;
Clear();
}
}
//-----------------------------------------------------------------------------
// Purpose: If the string is on the stack, move it to the heap.
// create a null heap string if memory can't be allocated.
// Callers of this /need/ the string to be in the heap state
// when done.
//-----------------------------------------------------------------------------
bool CUtlStringBuilder::Data::MoveToHeap()
{
bool bSuccess = true;
if (!IsHeap())
{
// try to recover the string at the point of failure, to help with debugging
size_t nLen = Length();
char *pszHeapString = (char*)malloc(nLen + 1);
if (pszHeapString)
{
// get the string copy before corrupting the stack union
char *pszStackString = Access();
memcpy(pszHeapString, pszStackString, nLen);
pszHeapString[nLen] = 0;
Heap.m_pchString = pszHeapString;
Heap.m_nLength = (uint32)nLen;
Heap.m_nCapacity = (uint32)nLen;
Heap.sentinel = STRING_TYPE_SENTINEL;
}
else
{
Heap.m_pchString = NULL;
Heap.m_nLength = 0;
Heap.m_nCapacity = 0;
bSuccess = false;
Heap.sentinel = (STRING_TYPE_SENTINEL | STRING_TYPE_ERROR);
}
}
return bSuccess;
}