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
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;
|
|
}
|