etqw-sdk/source/idlib/text/Str.cpp
2008-05-29 00:00:00 +00:00

2909 lines
58 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "../precompiled.h"
#pragma hdrstop
#if defined( MACOS_X )
#pragma GCC visibility push(hidden)
#endif
stringDataAllocator_t* idStr::stringDataAllocator;
bool idStr::stringAllocatorIsShared;
struct ShutdownStringAllocator {
~ShutdownStringAllocator() {
idStr::ShutdownMemory();
}
};
#if defined( MACOS_X )
#pragma GCC visibility pop
#endif
struct strColor_t {
idVec4 color;
const char* str;
};
#if defined( MACOS_X )
#pragma GCC visibility push(hidden)
#endif
static ShutdownStringAllocator shutdownStringAllocator;
#if defined( MACOS_X )
#pragma GCC visibility pop
#endif
idStr::hmsFormat_t idStr::defaultHMSFormat;
strColor_t g_color_table[COLOR_BITS+1] = {
{ idVec4( 0.0f, 0.0f, 0.0f, 1.0f ), "^0" }, // 0 - S_COLOR_DEFAULT 0
{ idVec4( 1.0f, 0.0f, 0.0f, 1.0f ), "^1" }, // 1 - S_COLOR_RED 1
{ idVec4( 0.0f, 1.0f, 0.0f, 1.0f ), "^2" }, // 2 - S_COLOR_GREEN 2
{ idVec4( 1.0f, 1.0f, 0.0f, 1.0f ), "^3" }, // 3 - S_COLOR_YELLOW 3
{ idVec4( 0.0f, 0.0f, 1.0f, 1.0f ), "^4" }, // 4 - S_COLOR_BLUE 4
{ idVec4( 0.0f, 1.0f, 1.0f, 1.0f ), "^5" }, // 5 - S_COLOR_CYAN 5
{ idVec4( 1.0f, 0.0f, 1.0f, 1.0f ), "^6" }, // 6 - S_COLOR_MAGENTA 6
{ idVec4( 1.0f, 1.0f, 1.0f, 1.0f ), "^7" }, // 7 - S_COLOR_WHITE 7
{ idVec4( 0.5f, 0.5f, 0.5f, 1.0f ), "^8" }, // 8 - S_COLOR_GRAY 8
{ idVec4( 0.15f, 0.15f, 0.15f, 1.0f ), "^9" }, // 9 - S_COLOR_BLACK 9
{ idVec4( 0.75f, 0.75f, 0.75f, 1.0f ), "^:" }, // : - lt.grey 10
{ idVec4( 0.25f, 0.25f, 0.25f, 1.0f ), "^;" }, // ; - dk.grey 11
{ idVec4( 0.0f, 0.5f, 0.0f, 1.0f ), "^<" }, // < - md.green 12
{ idVec4( 0.5f, 0.5f, 0.0f, 1.0f ), "^=" }, // = - md.yellow 13
{ idVec4( 0.0f, 0.0f, 0.5f, 1.0f ), "^>" }, // > - md.blue 14
{ idVec4( 0.5f, 0.0f, 0.0f, 1.0f ), "^?" }, // ? - md.red 15
{ idVec4( 0.5f, 0.25f, 0.0f, 1.0f ), "^@" }, // @ - md.orange 16
{ idVec4( 1.0f, 0.6f, 0.1f, 1.0f ), "^A" }, // A - lt.orange 17
{ idVec4( 0.0f, 0.5f, 0.5f, 1.0f ), "^B" }, // B - md.cyan 18
{ idVec4( 0.5f, 0.0f, 0.5f, 1.0f ), "^C" }, // C - md.purple 19
{ idVec4( 1.0f, 0.5f, 0.0f, 1.0f ), "^D" }, // D - orange 20
{ idVec4( 0.5f, 0.0f, 1.0f, 1.0f ), "^E" }, // E 21
{ idVec4( 0.2f, 0.6f, 0.8f, 1.0f ), "^F" }, // F 22
{ idVec4( 0.8f, 1.0f, 0.8f, 1.0f ), "^G" }, // G 23
{ idVec4( 0.0f, 0.4f, 0.2f, 1.0f ), "^H" }, // H 24
{ idVec4( 1.0f, 0.0f, 0.2f, 1.0f ), "^I" }, // I 25
{ idVec4( 0.7f, 0.1f, 0.1f, 1.0f ), "^J" }, // J 26
{ idVec4( 0.6f, 0.2f, 0.0f, 1.0f ), "^K" }, // K 27
{ idVec4( 0.8f, 0.6f, 0.2f, 1.0f ), "^L" }, // L 28
{ idVec4( 0.6f, 0.6f, 0.2f, 1.0f ), "^M" }, // M 29
{ idVec4( 1.0f, 1.0f, 0.75f, 1.0f ), "^N" }, // N 30
{ idVec4( 1.0f, 1.0f, 0.5f, 1.0f ), "^O" }, // O 31
};
dword g_dword_color_table[COLOR_BITS+1] = {
#if defined( _XENON ) || ( defined( MACOS_X ) && defined( __ppc__ ) )
0x000000FF, // S_COLOR_DEFAULT
0xFF0000FF, // S_COLOR_RED
0x00FF00FF, // S_COLOR_GREEN
0xFFFF00FF, // S_COLOR_YELLOW
0x0000FFFF, // S_COLOR_BLUE
0x00FFFFFF, // S_COLOR_CYAN
0xFF00FFFF, // S_COLOR_MAGENT
0xFFFFFFFF, // S_COLOR_WHITE
0x7F7F7FFF, // S_COLOR_GRAY
0x121212FF, // S_COLOR_BLACK
0xBFBFBFFF,
0x404040FF,
0x007F00FF,
0x7F7F00FF,
0x00007FFF,
0x7F0000FF,
0x7F3F00FF,
0xFF9919FF,
0x007F7FFF,
0x7F007FFF,
0xFF7F00FF,
0x7F00FFFF,
0x3399CCFF,
0xCCFFCCFF,
0x006633FF,
0xFF0033FF,
0xB21919FF,
0x993300FF,
0xCC9933FF,
0x999933FF,
0xFFFFBFFF,
0xFFFF7FFF
#elif defined( _WIN32 ) || defined( __linux__ ) || ( defined( MACOS_X ) && !defined( __ppc__ ) )
0xFF000000, // S_COLOR_DEFAULT
0xFF0000FF, // S_COLOR_RED
0xFF00FF00, // S_COLOR_GREEN
0xFF00FFFF, // S_COLOR_YELLOW
0xFFFF0000, // S_COLOR_BLUE
0xFFFFFF00, // S_COLOR_CYAN
0xFFFF00FF, // S_COLOR_MAGENT
0xFFFFFFFF, // S_COLOR_WHITE
0xFF7F7F7F, // S_COLOR_GRAY
0xFF212121, // S_COLOR_BLACK
0xFFBFBFBF,
0xFF040404,
0xFF007F00,
0xFF007F7F,
0xFF7F0000,
0xFF00007F,
0xFF003F7F,
0xFF1999FF,
0xFF7F7F00,
0xFF7F007F,
0xFF007FFF,
0xFFFF007F,
0xFFCC9933,
0xFFCCFFCC,
0xFF336600,
0xFF3300FF,
0xFF1919B2,
0xFF003399,
0xFF3399CC,
0xFF339999,
0xFFBFFFFF,
0xFF7FFFFF
#else
#error OS define is required!
#endif
};
const char *units[2][4] =
{
{ "B", "KB", "MB", "GB" },
{ "B/s", "KB/s", "MB/s", "GB/s" }
};
/*
============
idStr::ColorForIndex
============
*/
const idVec4& idStr::ColorForIndex( int i ) {
return g_color_table[ i & COLOR_BITS ].color;
}
/*
============
idStr::ColorForChar
============
*/
const idVec4& idStr::ColorForChar( int c ) {
return g_color_table[ ColorIndex( c ) ].color;
}
/*
============
idStr::StrForColorIndex
============
*/
const char* idStr::StrForColorIndex( int i ) {
return g_color_table[ i & COLOR_BITS ].str;
}
/*
============
idStr::ReAllocate
============
*/
void idStr::ReAllocate( int amount, bool keepold ) {
char *newbuffer;
int newsize;
int mod;
bool staticBuffer = alloced < 0;
//assert( data );
assert( amount > 0 );
mod = amount % STR_ALLOC_GRAN;
if ( !mod ) {
newsize = amount;
} else {
newsize = amount + STR_ALLOC_GRAN - mod;
}
alloced = newsize;
newbuffer = stringDataAllocator->Alloc( alloced );
if ( keepold && data ) {
if ( len ) {
strncpy( newbuffer, data, len );
newbuffer[ len ] = '\0';
} else {
newbuffer[0] = '\0';
}
}
if ( data && !staticBuffer /*data != baseBuffer*/ ) {
stringDataAllocator->Free( data );
}
data = newbuffer;
}
/*
============
idStr::FreeData
============
*/
void idStr::FreeData( void ) {
bool staticBuffer = alloced < 0;
if ( data && !staticBuffer ) {
stringDataAllocator->Free( data );
data = baseBuffer;
alloced = -STR_ALLOC_BASE;
len = 0;
}
}
void idStr::SetStaticBuffer( char *buffer, int length ) {
bool staticBuffer = alloced < 0;
if ( data && !staticBuffer ) {
stringDataAllocator->Free( data );
}
data = buffer;
alloced = -length;
len = 0;
}
/*
============
idStr::operator=
============
*/
void idStr::operator=( const char *text ) {
int l;
int diff;
int i;
if ( !text ) {
// safe behaviour if NULL
EnsureAlloced( 1, false );
data[ 0 ] = '\0';
len = 0;
return;
}
if ( text == data ) {
return; // copying same thing
}
// check if we're aliasing
if ( text >= data && text <= data + len ) {
diff = text - data;
assert( idStr::Length( text ) < (int)len );
for ( i = 0; text[ i ]; i++ ) {
data[ i ] = text[ i ];
}
data[ i ] = '\0';
len -= diff;
return;
}
l = Length( text );
EnsureAlloced( l + 1, false );
strcpy( data, text );
len = l;
}
/*
============
idStr::FindChar
returns INVALID_POSITION if not found otherwise the index of the char
============
*/
int idStr::FindChar( const char *str, const char c, int start, int end ) {
int i;
if ( end == INVALID_POSITION ) {
end = Length( str ) - 1;
}
for ( i = start; i <= end; i++ ) {
if ( str[i] == c ) {
return i;
}
}
return INVALID_POSITION;
}
/*
============
idStr::FindText
returns INVALID_POSITION if not found otherwise the index of the text
============
*/
int idStr::FindText( const char *str, const char *text, bool casesensitive, int start, int end ) {
int l, i, j;
if ( end == INVALID_POSITION ) {
end = Length( str );
}
l = end - Length( text );
for ( i = start; i <= l; i++ ) {
if ( casesensitive ) {
for ( j = 0; text[j]; j++ ) {
if ( str[i+j] != text[j] ) {
break;
}
}
} else {
for ( j = 0; text[j]; j++ ) {
if ( ::toupper( str[i+j] ) != ::toupper( text[j] ) ) {
break;
}
}
}
if ( !text[j] ) {
return i;
}
}
return INVALID_POSITION;
}
/*
============
idStr::CountChar
============
*/
int idStr::CountChar( const char *str, const char c ) {
int i, count = 0;
for ( i = 0; str[i] != '\0'; i++ ) {
if ( str[i] == c ) {
count++;
}
}
return count;
}
/*
============
idStr::Filter
Returns true if the string conforms the given filter.
Several metacharacter may be used in the filter.
* match any string of zero or more characters
? match any single character
[abc...] match any of the enclosed characters; a hyphen can
be used to specify a range (e.g. a-z, A-Z, 0-9)
============
*/
bool idStr::Filter( const char *filter, const char *name, bool casesensitive ) {
idStr buf;
int i, found, index;
while(*filter) {
if (*filter == '*') {
filter++;
buf.Empty();
for (i = 0; *filter; i++) {
if ( *filter == '*' || *filter == '?' || (*filter == '[' && *(filter+1) != '[') ) {
break;
}
buf += *filter;
if ( *filter == '[' ) {
filter++;
}
filter++;
}
if ( buf.Length() ) {
index = idStr(name).Find( buf.c_str(), casesensitive );
if ( index == INVALID_POSITION ) {
return false;
}
name += index + idStr::Length( buf );
}
}
else if (*filter == '?') {
filter++;
name++;
}
else if (*filter == '[') {
if ( *(filter+1) == '[' ) {
if ( *name != '[' ) {
return false;
}
filter += 2;
name++;
}
else {
filter++;
found = false;
while(*filter && !found) {
if (*filter == ']' && *(filter+1) != ']') {
break;
}
if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) {
if (casesensitive) {
if (*name >= *filter && *name <= *(filter+2)) {
found = true;
}
}
else {
if ( ::toupper(*name) >= ::toupper(*filter) && ::toupper(*name) <= ::toupper(*(filter+2)) ) {
found = true;
}
}
filter += 3;
}
else {
if (casesensitive) {
if (*filter == *name) {
found = true;
}
}
else {
if ( ::toupper(*filter) == ::toupper(*name) ) {
found = true;
}
}
filter++;
}
}
if (!found) {
return false;
}
while(*filter) {
if ( *filter == ']' && *(filter+1) != ']' ) {
break;
}
filter++;
}
filter++;
name++;
}
}
else {
if (casesensitive) {
if (*filter != *name) {
return false;
}
}
else {
if ( ::toupper(*filter) != ::toupper(*name) ) {
return false;
}
}
filter++;
name++;
}
}
return true;
}
/*
=============
idStr::StripMediaName
makes the string lower case, replaces backslashes with forward slashes, and removes extension
=============
*/
void idStr::StripMediaName( const char *name, idStr &mediaName ) {
char c;
mediaName.Empty();
for ( c = *name; c; c = *(++name) ) {
// truncate at an extension
if ( c == '.' ) {
break;
}
// convert backslashes to forward slashes
if ( c == '\\' ) {
mediaName.Append( '/' );
} else {
mediaName.Append( ToLower( c ) );
}
}
}
/*
=============
idStr::CheckExtension
=============
*/
bool idStr::CheckExtension( const char *name, const char *ext ) {
const char *s1 = name + Length( name ) - 1;
const char *s2 = ext + Length( ext ) - 1;
char c1, c2, d;
do {
c1 = *s1--;
c2 = *s2--;
d = c1 - c2;
while( d ) {
if ( c1 <= 'Z' && c1 >= 'A' ) {
d += ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c2 <= 'Z' && c2 >= 'A' ) {
d -= ('a' - 'A');
if ( !d ) {
break;
}
}
return false;
}
} while( s1 >= name && s2 >= ext );
return ( s1 >= name ) && ( *s1 == '.' );
}
/*
=============
idStr::FloatArrayToString
=============
*/
const char *idStr::FloatArrayToString( const float *array, const int length, const int precision ) {
static int index = 0;
static char str[4][16384]; // in case called by nested functions
int i, n;
char format[16], *s;
// use an array of string so that multiple calls won't collide
s = str[ index ];
index = (index + 1) & 3;
snPrintf( format, sizeof( format ), "%%.%df", precision );
n = snPrintf( s, sizeof( str[0] ), format, array[0] );
if ( precision > 0 ) {
while( n > 0 && s[n-1] == '0' ) s[--n] = '\0';
while( n > 0 && s[n-1] == '.' ) s[--n] = '\0';
}
snPrintf( format, sizeof( format ), " %%.%df", precision );
for ( i = 1; i < length; i++ ) {
n += snPrintf( s + n, sizeof( str[0] ) - n, format, array[i] );
if ( precision > 0 ) {
while( n > 0 && s[n-1] == '0' ) s[--n] = '\0';
while( n > 0 && s[n-1] == '.' ) s[--n] = '\0';
}
}
return s;
}
/*
===============
idStr::NumLoneyLF
===============
*/
int idStr::NumLonelyLF( const char *src ) {
int n = 0;
for ( ; *src != 0x00; src++ ) {
if ( *src == '\n' && *( src -1 ) != '\r' ) {
n++;
}
}
return n;
}
/*
===============
idStr::ToCRLF
===============
*/
bool idStr::ToCRLF( const char *src, char *dest, int maxLength ) {
// copy, turning lonely linefeeds into CR\LF
int j = 0;
for ( int i = 0; src[ i ] != 0x00 && j < maxLength - 1; i++, j++ ) {
int ch = src[ i ];
if ( ch == '\n' && src[ i - 1 ] != '\r' ) {
dest[ j ] = '\r';
dest[ ++j ] = '\n';
} else {
dest[ j ] = ch;
}
}
// 0 terminate
if ( j < maxLength ) {
dest[ j ] = 0x00;
return true;
}
dest[ maxLength - 1 ] = 0x00;
return false;
}
/*
===============
idStr::CStyleQuote
===============
*/
const char *idStr::CStyleQuote( const char *str ) {
static int index = 0;
static char buffers[4][16384]; // in case called by nested functions
unsigned int i;
char *buf;
buf = buffers[index];
index = ( index + 1 ) & 3;
buf[0] = '\"';
for ( i = 1; i < sizeof( buffers[0] ) - 2; i++ ) {
int c = *str++;
switch( c ) {
case '\0': buf[i++] = '\"'; buf[i] = '\0'; return buf;
case '\\': buf[i++] = '\\'; buf[i] = '\\'; break;
case '\n': buf[i++] = '\\'; buf[i] = 'n'; break;
case '\r': buf[i++] = '\\'; buf[i] = 'r'; break;
case '\t': buf[i++] = '\\'; buf[i] = 't'; break;
case '\v': buf[i++] = '\\'; buf[i] = 'v'; break;
case '\b': buf[i++] = '\\'; buf[i] = 'b'; break;
case '\f': buf[i++] = '\\'; buf[i] = 'f'; break;
case '\a': buf[i++] = '\\'; buf[i] = 'a'; break;
case '\'': buf[i++] = '\\'; buf[i] = '\''; break;
case '\"': buf[i++] = '\\'; buf[i] = '\"'; break;
case '\?': buf[i++] = '\\'; buf[i] = '\?'; break;
default: buf[i] = c; break;
}
}
buf[i++] = '\"';
buf[i] = '\0';
return buf;
}
/*
===============
idStr::CStyleUnQuote
===============
*/
const char *idStr::CStyleUnQuote( const char *str ) {
static int index = 0;
static char buffers[4][16384]; // in case called by nested functions
unsigned int i;
char *buf;
buf = buffers[index];
index = ( index + 1 ) & 3;
assert( str[0] == '\"' );
str++;
for ( i = 0; i < sizeof( buffers[0] ) - 1; i++ ) {
int c = *str++;
if ( c == '\0' ) {
break;
} else if ( c == '\\' ) {
c = *str++;
switch( c ) {
case '\\': buf[i] = '\\'; break;
case 'n': buf[i] = '\n'; break;
case 'r': buf[i] = '\r'; break;
case 't': buf[i] = '\t'; break;
case 'v': buf[i] = '\v'; break;
case 'b': buf[i] = '\b'; break;
case 'f': buf[i] = '\f'; break;
case 'a': buf[i] = '\a'; break;
case '\'': buf[i] = '\''; break;
case '\"': buf[i] = '\"'; break;
case '\?': buf[i] = '\?'; break;
}
} else {
buf[i] = c;
}
}
assert( buf[i-1] == '\"' );
buf[i-1] = '\0';
return buf;
}
/*
============
idStr::Last
returns INVALID_POSITION if not found otherwise the index of the char
============
*/
int idStr::Last( const char c, int index ) const {
if( index == INVALID_POSITION ) {
index = Length();
}
for( ; index >= 0; index-- ) {
if ( data[ index ] == c ) {
return index;
}
}
return INVALID_POSITION;
}
/*
============
idStr::Last
returns INVALID_POSITION if not found otherwise the index of the string
============
*/
int idStr::Last( const char* str, bool casesensitive, int index ) const {
if( index == INVALID_POSITION ) {
index = Length();
}
int searchLength = Length( str );
if( len - index > searchLength ) {
index -= searchLength;
}
for( ; index >= 0; index-- ) {
if( ( casesensitive && Cmpn( &data[ index ], str, searchLength ) == 0 ) ||
( !casesensitive && Icmpn( &data[ index ], str, searchLength ) == 0 )) {
return index;
}
}
return INVALID_POSITION;
}
/*
============
idStr::StripLeading
============
*/
void idStr::StripLeading( const char c ) {
while( data[ 0 ] == c ) {
memmove( &data[ 0 ], &data[ 1 ], len );
len--;
}
}
/*
============
idStr::StripLeading
============
*/
void idStr::StripLeading( const char *string ) {
int l;
l = Length( string );
if ( l > 0 ) {
while ( !Cmpn( string, l ) ) {
memmove( data, data + l, len - l + 1 );
len -= l;
}
}
}
/*
============
idStr::StripLeadingOnce
============
*/
bool idStr::StripLeadingOnce( const char *string ) {
int l;
l = Length( string );
if ( ( l > 0 ) && !Cmpn( string, l ) ) {
memmove( data, data + l, len - l + 1 );
len -= l;
return true;
}
return false;
}
/*
============
idStr::StripTrailing
============
*/
void idStr::StripTrailing( const char c ) {
int i;
for( i = Length(); i > 0 && data[ i - 1 ] == c; i-- ) {
data[ i - 1 ] = '\0';
len--;
}
}
/*
============
idStr::StripLeading
============
*/
void idStr::StripTrailing( const char *string ) {
int l;
l = Length( string );
if ( l > 0 ) {
while ( ( len >= l ) && !Cmpn( string, data + len - l, l ) ) {
len -= l;
data[len] = '\0';
}
}
}
/*
============
idStr::StripTrailingOnce
============
*/
bool idStr::StripTrailingOnce( const char *string ) {
int l;
l = Length( string );
if ( ( l > 0 ) && ( len >= l ) && !Cmpn( string, data + len - l, l ) ) {
len -= l;
data[len] = '\0';
return true;
}
return false;
}
/*
============
idStr::ReplaceChar
============
*/
void idStr::ReplaceChar( char oldChar, char newChar ) {
int i;
for ( i = 0; i < len; i++ ) {
if ( data[ i ] != oldChar ) {
continue;
}
data[ i ] = newChar;
}
}
/*
============
idStr::Replace
============
*/
void idStr::Replace( const char *old, const char *nw ) {
if ( Length( old ) == 0 ) {
return;
}
int oldLen, newLen, i, count;
oldLen = Length( old );
newLen = Length( nw );
// Work out how big the new string will be
count = 0;
for ( i = 0; i < len; i++ ) {
if( !Cmpn( &data[i], old, oldLen ) ) {
count++;
i += oldLen - 1;
}
}
if ( count ) {
idStr oldString( data );
int j;
EnsureAlloced( len + ( ( newLen - oldLen ) * count ) + 2, false );
// Replace the old data with the new data
for ( i = 0, j = 0; i < oldString.Length(); i++ ) {
if ( !Cmpn( &oldString[i], old, oldLen ) ) {
memcpy( data + j, nw, newLen );
i += oldLen - 1;
j += newLen;
} else {
data[j] = oldString[i];
j++;
}
}
data[j] = '\0';
len = Length( data );
}
}
/*
============
idStr::ReplaceFirst
============
*/
void idStr::ReplaceFirst( const char *old, const char *nw ) {
if( Length( old ) == 0 ) {
return;
}
int oldLen, newLen, i;
bool present;
oldLen = Length( old );
newLen = Length( nw );
// Work out how big the new string will be
present = false;
for ( i = 0; i < len; i++ ) {
if ( !Cmpn( &data[i], old, oldLen ) ) {
present = true;
i += oldLen - 1;
break;
}
}
if ( present ) {
idStr oldString( data );
int j;
EnsureAlloced( len + ( newLen - oldLen ) + 2, false );
// Replace the old data with the new data
for ( i = 0, j = 0; i < oldString.Length(); i++ ) {
if ( !Cmpn( &oldString[i], old, oldLen ) ) {
memcpy( data + j, nw, newLen );
i += oldLen;
j += newLen;
break;
} else {
data[j] = oldString[i];
j++;
}
}
memcpy( data + j, &oldString[i], oldString.Length() - i );
data[j + oldString.Length() - i] = '\0';
len = Length( data );
}
}
/*
============
idStr::Mid
============
*/
const char *idStr::Mid( int start, int len, idStr &result ) const {
int i;
assert( &result != this );
result.Empty();
i = Length();
if ( i == 0 || len <= 0 || start >= i ) {
return NULL;
}
if ( start + len >= i ) {
len = i - start;
}
result.Append( &data[ start ], len );
return result;
}
/*
============
idStr::Mid
============
*/
idStr idStr::Mid( int start, int len ) const {
int i;
idStr result;
i = Length();
if ( i == 0 || len <= 0 || start >= i ) {
return result;
}
if ( start + len >= i ) {
len = i - start;
}
result.Append( &data[ start ], len );
return result;
}
/*
============
idStr::StripLeadingWhiteSpace
============
*/
void idStr::StripLeadingWhiteSpace( void ) {
int i;
// cast to unsigned char to prevent stripping off high-ASCII characters
for ( i = 0; i < Length() && (unsigned char)(data[ i ]) <= ' '; i++ );
if ( i > 0 && i != Length() ) {
memmove( data, data + i, len - i + 1 );
len -= i;
}
}
/*
============
idStr::StripTrailingWhiteSpace
============
*/
void idStr::StripTrailingWhiteSpace( void ) {
int i;
// cast to unsigned char to prevent stripping off high-ASCII characters
for ( i = Length(); i > 0 && (unsigned char)(data[ i - 1 ]) <= ' '; i-- ) {
data[ i - 1 ] = '\0';
len--;
}
}
/*
============
idStr::StripQuotes
Removes the quotes from the beginning and end of the string
============
*/
idStr& idStr::StripQuotes ( void )
{
if ( data[0] != '\"' )
{
return *this;
}
// Remove the trailing quote first
if ( data[len-1] == '\"' )
{
data[len-1] = '\0';
len--;
}
// Strip the leading quote now
len--;
memmove( &data[ 0 ], &data[ 1 ], len );
data[len] = '\0';
return *this;
}
/*
=====================================================================
filename methods
=====================================================================
*/
/*
============
idStr::FileNameHash
============
*/
int idStr::FileNameHash( const char *string, const int hashSize ) {
int i;
long hash;
char letter;
hash = 0;
i = 0;
while( string[i] != '\0' ) {
letter = idStr::ToLower( string[i] );
if ( letter == '.' ) {
break; // don't include extension
}
if ( letter =='\\' ) {
letter = '/';
}
hash += (long)letter * ( i + 119 );
i++;
}
hash &= ( hashSize - 1 );
return hash;
}
/*
============
idStr::BackSlashesToSlashes
============
*/
idStr &idStr::BackSlashesToSlashes( void ) {
int i;
for ( i = 0; i < len; i++ ) {
if ( data[ i ] == '\\' ) {
data[ i ] = '/';
}
}
return *this;
}
/*
============
idStr::SlashesToBackSlashes
============
*/
idStr &idStr::SlashesToBackSlashes( void ) {
int i;
for ( i = 0; i < len; i++ ) {
if ( data[ i ] == '/' ) {
data[ i ] = '\\';
}
}
return *this;
}
/*
============
idStr::CollapsePath
Removes '..' from path and changes backslashes to slashes.
Example:
W:/ETQW/base/../code/game/../idlib/../game/Game_local.h
Becomes:
W:/ETQW/code/game/Game_local.h
============
*/
idStr &idStr::CollapsePath( void ) {
int i, length = 0;
for ( i = 0; i < len; i++ ) {
if ( data[i] == '.' ) {
if ( data[i+1] == '.' && ( data[i+2] == '/' || data[i+2] == '\\' ) ) { // ../
if ( length >= 2 && ( data[length-1] == '/' || data[length-1] == '\\' ) ) {
if ( length == 2 || data[length-2] != '.' || data[length-3] != '.' ) {
length--;
while( length > 0 && data[length-1] != '/' && data[length-1] != '\\' ) {
length--;
}
i += 2;
continue;
}
}
data[length++] = data[i++];
data[length++] = data[i++];
data[length++] = data[i];
} else if ( data[i+1] == '/' || data[i+1] == '\\' ) { // ./
i++;
} else {
data[length++] = data[i];
}
} else {
data[length++] = data[i];
}
}
data[length] = '\0';
len = length;
return *this;
}
/*
============
idStr::SetFileExtension
============
*/
idStr &idStr::SetFileExtension( const char *extension ) {
StripFileExtension();
if ( *extension != '.' ) {
Append( '.' );
}
Append( extension );
return *this;
}
/*
============
idStr::StripFileExtension
============
*/
idStr &idStr::StripFileExtension( void ) {
int i;
for ( i = len-1; i >= 0; i-- ) {
if ( data[i] == '/' || data[i] == '\\' ) {
break;
}
if ( data[i] == '.' ) {
data[i] = '\0';
len = i;
break;
}
}
return *this;
}
/*
============
idStr::StripAbsoluteFileExtension
============
*/
idStr &idStr::StripAbsoluteFileExtension( void ) {
int i;
for ( i = 0; i < len; i++ ) {
if ( data[i] == '.' ) {
data[i] = '\0';
len = i;
break;
}
}
return *this;
}
/*
==================
idStr::DefaultFileExtension
==================
*/
idStr &idStr::DefaultFileExtension( const char *extension ) {
int i;
// do nothing if the string already has an extension
for ( i = len-1; i >= 0; i-- ) {
if ( data[i] == '.' ) {
return *this;
}
}
if ( *extension != '.' ) {
Append( '.' );
}
Append( extension );
return *this;
}
/*
==================
idStr::DefaultPath
==================
*/
idStr &idStr::DefaultPath( const char *basepath ) {
if ( ( ( *this )[ 0 ] == '/' ) || ( ( *this )[ 0 ] == '\\' ) ) {
// absolute path location
return *this;
}
*this = basepath + *this;
return *this;
}
/*
====================
idStr::AppendPath
====================
*/
void idStr::AppendPath( const char *text ) {
int pos;
int i = 0;
if ( text && text[i] ) {
pos = len;
EnsureAlloced( len + Length( text ) + 2 );
if ( pos ) {
if ( data[ pos-1 ] != '/' && data[ pos-1 ] != '\\' ) {
data[ pos++ ] = '/';
}
}
if ( text[i] == '/' ) {
i++;
}
for ( ; text[ i ]; i++ ) {
if ( text[ i ] == '\\' ) {
data[ pos++ ] = '/';
} else {
data[ pos++ ] = text[ i ];
}
}
len = pos;
data[ pos ] = '\0';
}
}
/*
==================
idStr::StripFilename
==================
*/
idStr &idStr::StripFilename( void ) {
int pos;
pos = Length() - 1;
while( ( pos > 0 ) && ( ( *this )[ pos ] != '/' ) && ( ( *this )[ pos ] != '\\' ) ) {
pos--;
}
if ( pos < 0 ) {
pos = 0;
}
CapLength( pos );
return *this;
}
/*
==================
idStr::StripPath
==================
*/
idStr &idStr::StripPath( void ) {
int pos;
pos = Length();
while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
pos--;
}
*this = Right( Length() - pos );
return *this;
}
/*
====================
idStr::ExtractFilePath
====================
*/
void idStr::ExtractFilePath( idStr &dest ) const {
int pos;
//
// back up until a \ or the start
//
pos = Length();
while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
pos--;
}
Left( pos, dest );
}
/*
====================
idStr::ExtractFileName
====================
*/
void idStr::ExtractFileName( idStr &dest ) const {
int pos;
//
// back up until a \ or the start
//
pos = Length() - 1;
while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
pos--;
}
Right( Length() - pos, dest );
}
/*
====================
idStr::ExtractFileBase
====================
*/
void idStr::ExtractFileBase( idStr &dest ) const {
int pos;
int start;
//
// back up until a \ or the start
//
pos = Length() - 1;
while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
pos--;
}
start = pos;
while( ( pos < Length() ) && ( ( *this )[ pos ] != '.' ) ) {
pos++;
}
Mid( start, pos - start, dest );
}
/*
====================
idStr::ExtractFileExtension
====================
*/
void idStr::ExtractFileExtension( idStr &dest ) const {
int pos;
//
// back up until a . or the start
//
pos = Length() - 1;
while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '.' ) ) {
pos--;
}
if ( !pos ) {
// no extension
dest.Empty();
} else {
Right( Length() - pos, dest );
}
}
/*
============
idStr::MakeNameCanonical
============
*/
void idStr::MakeNameCanonical( void ) {
ToLower();
BackSlashesToSlashes();
StripFileExtension();
}
/*
=====================================================================
char * methods to replace library functions
=====================================================================
*/
/*
============
idStr::IsNumeric
Checks a string to see if it contains only numerical values.
============
*/
bool idStr::IsNumeric( const char *s ) {
int i;
bool dot;
if ( *s == '-' ) {
s++;
}
dot = false;
for ( i = 0; s[i]; i++ ) {
if ( !isdigit( s[i] ) ) {
if ( ( s[ i ] == '.' ) && !dot ) {
dot = true;
continue;
}
return false;
}
}
return true;
}
/*
============
idStr::HasLower
Checks if a string has any lowercase chars
============
*/
bool idStr::HasLower( const char *s ) {
if ( !s ) {
return false;
}
while ( *s ) {
if ( CharIsLower( *s ) ) {
return true;
}
s++;
}
return false;
}
/*
============
idStr::HasUpper
Checks if a string has any uppercase chars
============
*/
bool idStr::HasUpper( const char *s ) {
if ( !s ) {
return false;
}
while ( *s ) {
if ( CharIsUpper( *s ) ) {
return true;
}
s++;
}
return false;
}
/*
================
idStr::Cmp
================
*/
int idStr::Cmp( const char *s1, const char *s2 ) {
int c1, c2, d;
do {
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
if ( d ) {
return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
}
} while( c1 );
return 0; // strings are equal
}
/*
================
idStr::Cmpn
================
*/
int idStr::Cmpn( const char *s1, const char *s2, int n ) {
int c1, c2, d;
assert( n >= 0 );
do {
c1 = *s1++;
c2 = *s2++;
if ( !n-- ) {
return 0; // strings are equal until end point
}
d = c1 - c2;
if ( d ) {
return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
}
} while( c1 );
return 0; // strings are equal
}
/*
================
idStr::Icmp
================
*/
int idStr::Icmp( const char *s1, const char *s2 ) {
int c1, c2, d;
do {
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
while( d ) {
if ( c1 <= 'Z' && c1 >= 'A' ) {
d += ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c2 <= 'Z' && c2 >= 'A' ) {
d -= ('a' - 'A');
if ( !d ) {
break;
}
}
return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
}
} while( c1 );
return 0; // strings are equal
}
/*
================
idStr::Icmpn
================
*/
int idStr::Icmpn( const char *s1, const char *s2, int n ) {
int c1, c2, d;
assert( n >= 0 );
do {
c1 = *s1++;
c2 = *s2++;
if ( !n-- ) {
return 0; // strings are equal until end point
}
d = c1 - c2;
while( d ) {
if ( c1 <= 'Z' && c1 >= 'A' ) {
d += ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c2 <= 'Z' && c2 >= 'A' ) {
d -= ('a' - 'A');
if ( !d ) {
break;
}
}
return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
}
} while( c1 );
return 0; // strings are equal
}
/*
================
idStr::Icmp
================
*/
int idStr::IcmpNoColor( const char *s1, const char *s2 ) {
int c1, c2, d;
do {
while ( IsColor( s1 ) ) {
s1 += 2;
}
while ( IsColor( s2 ) ) {
s2 += 2;
}
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
while( d ) {
if ( c1 <= 'Z' && c1 >= 'A' ) {
d += ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c2 <= 'Z' && c2 >= 'A' ) {
d -= ('a' - 'A');
if ( !d ) {
break;
}
}
return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
}
} while( c1 );
return 0; // strings are equal
}
/*
================
idStr::IcmpPath
================
*/
int idStr::IcmpPath( const char *s1, const char *s2 ) {
int c1, c2, d;
#if 0
//#if !defined( _WIN32 )
idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" );
#endif
do {
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
while( d ) {
if ( c1 <= 'Z' && c1 >= 'A' ) {
d += ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c1 == '\\' ) {
d += ('/' - '\\');
if ( !d ) {
break;
}
}
if ( c2 <= 'Z' && c2 >= 'A' ) {
d -= ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c2 == '\\' ) {
d -= ('/' - '\\');
if ( !d ) {
break;
}
}
// make sure folders come first
while( c1 ) {
if ( c1 == '/' || c1 == '\\' ) {
break;
}
c1 = *s1++;
}
while( c2 ) {
if ( c2 == '/' || c2 == '\\' ) {
break;
}
c2 = *s2++;
}
if ( c1 && !c2 ) {
return -1;
} else if ( !c1 && c2 ) {
return 1;
}
// same folder depth so use the regular compare
return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
}
} while( c1 );
return 0;
}
/*
================
idStr::IcmpnPath
================
*/
int idStr::IcmpnPath( const char *s1, const char *s2, int n ) {
int c1, c2, d;
#if 0
//#if !defined( _WIN32 )
idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" );
#endif
assert( n >= 0 );
do {
c1 = *s1++;
c2 = *s2++;
if ( !n-- ) {
return 0; // strings are equal until end point
}
d = c1 - c2;
while( d ) {
if ( c1 <= 'Z' && c1 >= 'A' ) {
d += ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c1 == '\\' ) {
d += ('/' - '\\');
if ( !d ) {
break;
}
}
if ( c2 <= 'Z' && c2 >= 'A' ) {
d -= ('a' - 'A');
if ( !d ) {
break;
}
}
if ( c2 == '\\' ) {
d -= ('/' - '\\');
if ( !d ) {
break;
}
}
// make sure folders come first
while( c1 ) {
if ( c1 == '/' || c1 == '\\' ) {
break;
}
c1 = *s1++;
}
while( c2 ) {
if ( c2 == '/' || c2 == '\\' ) {
break;
}
c2 = *s2++;
}
if ( c1 && !c2 ) {
return -1;
} else if ( !c1 && c2 ) {
return 1;
}
// same folder depth so use the regular compare
return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
}
} while( c1 );
return 0;
}
/*
=============
idStr::Copynz
Safe strncpy that ensures a trailing zero
NOTE: the specs indicate strncpy pads with zeros up to destination size, which be a bit wasteful
=============
*/
void idStr::Copynz( char *dest, const char *src, int destsize ) {
if ( !src ) {
idLib::common->Warning( "idStr::Copynz: NULL src" );
return;
}
if ( destsize < 1 ) {
idLib::common->Warning( "idStr::Copynz: destsize < 1" );
return;
}
strncpy( dest, src, destsize - 1 );
dest[ destsize - 1 ] = '\0';
}
/*
================
idStr::Append
never goes past bounds or leaves without a terminating 0
================
*/
void idStr::Append( char *dest, int size, const char *src ) {
int l1;
l1 = Length( dest );
if ( l1 >= size ) {
idLib::common->Error( "idStr::Append: already overflowed" );
}
Copynz( dest + l1, src, size - l1 );
}
/*
================
idStr::LengthWithoutColors
================
*/
int idStr::LengthWithoutColors( const char *s ) {
int len;
const char *p;
if ( !s ) {
return 0;
}
len = 0;
p = s;
while( *p ) {
if ( IsColor( p ) ) {
p += 2;
continue;
}
p++;
len++;
}
return len;
}
/*
================
idStr::RemoveColors
================
*/
char *idStr::RemoveColors( char *string ) {
char *d;
char *s;
int c;
s = string;
d = string;
while( (c = *s) != 0 ) {
if ( IsColor( s ) ) {
s++;
}
else {
*d++ = c;
}
s++;
}
*d = '\0';
return string;
}
/*
================
idStr::IsBadFilenameChar
================
*/
bool idStr::IsBadFilenameChar( char c ) {
static char badFilenameChars[] = { ':', ';', '&', '(', ')', '|', '<', '>', '*', '?', '[', ']', '~', '+', '@', '!', '\\', '/', ' ', '\t', '\'', '"', '\0' };
for ( int i = 0; badFilenameChars[i] != '\0'; i++ ) {
if ( c == badFilenameChars[i] ) {
return true;
}
}
return false;
}
/*
================
idStr::CleanFilename
================
*/
char* idStr::CleanFilename( char* string ) {
char* d;
char* s;
s = string;
d = string;
// clear leading .'s
while ( *s == '.' ) {
s++;
}
while ( *s != '\0' ) {
if ( !IsBadFilenameChar( *s ) ) {
*d++ = *s;
}
s++;
}
*d = '\0';
return string;
}
/*
================
idStr::StripFilename
================
*/
char* idStr::StripFilename( char* string ) {
int pos;
pos = idStr::Length( string ) - 1;
while( ( pos > 0 ) && ( string[ pos ] != '/' ) && ( string[ pos ] != '\\' ) ) {
pos--;
}
if ( pos < 0 ) {
pos = 0;
}
string[ pos ] = '\0';
return string;
}
/*
==================
idStr::StripPath
==================
*/
char* idStr::StripPath( char* string ) {
int pos, length;
length = pos = idStr::Length( string );
while( ( pos > 0 ) && ( string[ pos - 1 ] != '/' ) && ( string[ pos - 1 ] != '\\' ) ) {
pos--;
}
return &string[ pos ];
}
/*
================
idStr::snPrintf
see idStr::vsnPrintf
you can pass snPrintf( buffer, sizeof( buffer ) .. )
will return -1 on error or overflow (and warn)
upon overflow the string is written and truncated
returns the number of characters written, not including the terminal null
(terminating null character is always written, which means ret < size in all cases)
================
*/
int idStr::snPrintf( char *dest, int size, const char *fmt, ...) {
int ret;
va_list argptr;
#ifdef _WIN32
#undef _vsnprintf
va_start( argptr, fmt );
ret = _vsnprintf( dest, size-1, fmt, argptr );
va_end( argptr );
#define _vsnprintf use_idStr_vsnPrintf
#else
#undef vsnprintf
va_start( argptr, fmt );
ret = vsnprintf( dest, size, fmt, argptr );
va_end( argptr );
#define vsnprintf use_idStr_vsnPrintf
#endif
dest[size-1] = '\0';
if ( ret < 0 || ret >= size ) {
return -1;
}
return ret;
}
// pedestrian version: verbose and explicit implementation - just stick to the easier one
#if 0
int idStr::snPrintf( char *dest, int size, const char *fmt, ...) {
int len;
va_list argptr;
va_start( argptr, fmt );
// VC7 only has _vsnprintf
// VC8 adds vsnprintf, which does exactly the same
// which is bad because their implementation still isn't C99 compliant
#ifdef _WIN32
len = _vsnprintf( dest, size-1, fmt, argptr );
#else
len = vsnprintf( dest, size-1, fmt, argptr );
#endif
va_end( argptr );
if ( len < 0 ) {
#ifdef _WIN32
// unless _set_invalid_parameter_handler has been set to something other than default
// then this is very likely an overflow
idLib::common->Warning( "idStr::snPrintf: error or overflow %i", len );
#else
idLib::common->Warning( "idStr::snPrintf: error %i", len );
#endif
// put a terminating null character
dest[size-1] = '\0';
return -1;
}
if ( len >= size - 1 ) {
// on Linux systems this means the output was truncated at size-1
// (that is conformant to the C99 standard)
// windows systems will just return -1 on overflow (handled above)
// still, the retarded windows implementation may write exactly size - 1 characters and *not* put a terminating null character
#ifdef _WIN32
assert( len == size - 1 );
dest[size-1] = '\0';
return len;
#else
if ( len == size - 1 ) {
// fixup to match win32 behaviour
dest[size-1] = '\0';
return len;
}
idLib::common->Warning( "idStr::snPrintf: overflow of %i in %i", len, size );
dest[size-1] = '\0';
return -1;
#endif
}
return len;
}
#endif
/*
============
idStr::vsnPrintf
vsnprintf portability:
C99 standard: vsnprintf returns the number of characters (excluding the trailing
'\0') which would have been written to the final string if enough space had been available
snprintf and vsnprintf do not write more than size bytes (including the trailing '\0')
win32: _vsnprintf returns the number of characters written, not including the terminating null character,
or a negative value if an output error occurs. If the number of characters to write exceeds count, then count
characters are written and -1 is returned and no trailing '\0' is added.
idStr::vsnPrintf: always appends a trailing '\0', returns number of characters written (not including terminal \0)
or returns -1 on failure or if the buffer would be overflowed.
============
*/
int idStr::vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ) {
int ret;
#ifdef _WIN32
#undef _vsnprintf
ret = _vsnprintf( dest, size-1, fmt, argptr );
#define _vsnprintf use_idStr_vsnPrintf
#else
#undef vsnprintf
ret = vsnprintf( dest, size, fmt, argptr );
#define vsnprintf use_idStr_vsnPrintf
#endif
dest[size-1] = '\0';
if ( ret < 0 || ret >= size ) {
return -1;
}
return ret;
}
/*
===============
idStr::Test
test those snPrintf/vsnPrintf functions
mostly to check the behaviour between win32 and other platforms is the same
===============
*/
void idStr::Test( void ) {
char buffer[10];
int ret;
idStr test;
idLib::common->Printf( "idStr::Test\n" );
idStr::Copynz( buffer, "012345678", sizeof( buffer ) );
assert( buffer[9] == '\0' );
ret = test.snPrintf( buffer, 10, "%s", "876543210" );
assert( buffer[9] == '\0' );
idLib::common->Printf( "%d %s\n", ret, buffer );
ret = test.snPrintf( buffer, 10, "%s", "0123456789" );
assert( buffer[9] == '\0' );
idLib::common->Printf( "%d %s\n", ret, buffer );
}
/*
============
sprintf
Sets the value of the string using a printf interface.
============
*/
int sprintf( idStr &string, const char *fmt, ... ) {
int l;
va_list argptr;
char buffer[32000];
va_start( argptr, fmt );
l = idStr::vsnPrintf( buffer, sizeof(buffer), fmt, argptr );
va_end( argptr );
string = buffer;
return l;
}
/*
============
vsprintf
Sets the value of the string using a vprintf interface.
============
*/
int vsprintf( idStr &string, const char *fmt, va_list argptr ) {
int l;
char buffer[32000];
l = idStr::vsnPrintf( buffer, sizeof(buffer), fmt, argptr );
string = buffer;
return l;
}
/*
============
va
does a varargs printf into a temp buffer
NOTE: not thread safe
============
*/
char *va( const char *fmt, ... ) {
va_list argptr;
static int index = 0;
static char string[4][16384]; // in case called by nested functions
char *buf;
buf = string[index];
index = (index + 1) & 3;
va_start( argptr, fmt );
vsprintf( buf, fmt, argptr );
va_end( argptr );
return buf;
}
char *vva( char *buf, const char *fmt, ... ) {
va_list argptr;
va_start( argptr, fmt );
vsprintf( buf, fmt, argptr );
va_end( argptr );
return buf;
}
/*
=================
va_floatstring
=================
*/
char* va_floatstring( const char *fmt, ... ) {
va_list argPtr;
static int bufferIndex = 0;
static char string[4][16384]; // in case called by nested functions
char *buf;
buf = string[bufferIndex];
bufferIndex = (bufferIndex + 1) & 3;
long i;
unsigned long u;
double f;
char *str;
int index;
idStr tmp, format;
index = 0;
va_start( argPtr, fmt );
while( *fmt ) {
switch( *fmt ) {
case '%':
format = "";
format += *fmt++;
while ( (*fmt >= '0' && *fmt <= '9') ||
*fmt == '.' || *fmt == '-' || *fmt == '+' || *fmt == '#') {
format += *fmt++;
}
format += *fmt;
switch( *fmt ) {
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
f = va_arg( argPtr, double );
if ( format.Length() <= 2 ) {
// high precision floating point number without trailing zeros
sprintf( tmp, "%1.10f", f );
tmp.StripTrailing( '0' );
tmp.StripTrailing( '.' );
index += sprintf( buf+index, "%s", tmp.c_str() );
}
else {
index += sprintf( buf+index, format.c_str(), f );
}
break;
case 'd':
case 'i':
i = va_arg( argPtr, long );
index += sprintf( buf+index, format.c_str(), i );
break;
case 'u':
u = va_arg( argPtr, unsigned long );
index += sprintf( buf+index, format.c_str(), u );
break;
case 'o':
u = va_arg( argPtr, unsigned long );
index += sprintf( buf+index, format.c_str(), u );
break;
case 'x':
u = va_arg( argPtr, unsigned long );
index += sprintf( buf+index, format.c_str(), u );
break;
case 'X':
u = va_arg( argPtr, unsigned long );
index += sprintf( buf+index, format.c_str(), u );
break;
case 'c':
i = va_arg( argPtr, long );
index += sprintf( buf+index, format.c_str(), (char) i );
break;
case 's':
str = va_arg( argPtr, char * );
index += sprintf( buf+index, format.c_str(), str );
break;
case '%':
index += sprintf( buf+index, format.c_str() );
break;
default:
common->Error( "FS_WriteFloatString: invalid format %s", format.c_str() );
break;
}
fmt++;
break;
case '\\':
fmt++;
switch( *fmt ) {
case 't':
index += sprintf( buf+index, "\t" );
break;
case 'v':
index += sprintf( buf+index, "\v" );
break;
case 'n':
index += sprintf( buf+index, "\n" );
break;
case '\\':
index += sprintf( buf+index, "\\" );
break;
default:
common->Error( "FS_WriteFloatString: unknown escape character \'%c\'", *fmt );
break;
}
fmt++;
break;
default:
index += sprintf( buf+index, "%c", *fmt );
fmt++;
break;
}
}
va_end( argPtr );
return buf;
}
/*
============
idStr::BestUnit
============
*/
int idStr::BestUnit( const char *format, float value, measure_t measure ) {
int unit = 1;
while ( unit <= 3 && ( 1 << ( unit * 10 ) < value ) ) {
unit++;
}
unit--;
value /= 1 << ( unit * 10 );
sprintf( *this, format, value );
*this += " ";
*this += units[ measure ][ unit ];
return unit;
}
/*
============
idStr::SetUnit
============
*/
void idStr::SetUnit( const char *format, float value, int unit, measure_t measure ) {
value /= 1 << ( unit * 10 );
sprintf( *this, format, value );
*this += " ";
*this += units[ measure ][ unit ];
}
/*
================
idStr::InitMemory
================
*/
void idStr::InitMemory( void ) {
if( !stringDataAllocator ) {
stringDataAllocator = new stringDataAllocator_t;
stringDataAllocator->Init();
stringAllocatorIsShared = false;
}
}
/*
================
idStr::ShutdownMemory
================
*/
void idStr::ShutdownMemory( void ) {
if( stringDataAllocator && !stringAllocatorIsShared ) {
stringDataAllocator->Shutdown();
delete stringDataAllocator;
stringDataAllocator = NULL;
}
}
/*
================
idStr::PurgeMemory
================
*/
void idStr::PurgeMemory( void ) {
stringDataAllocator->FreeEmptyBaseBlocks();
}
/*
================
idStr::ShowMemoryUsage_f
================
*/
void idStr::ShowMemoryUsage_f( const idCmdArgs &args ) {
idLib::common->Printf( "%6d KB string memory (%d KB free in %d blocks, %d empty base blocks)\n",
stringDataAllocator->GetBaseBlockMemory() >> 10, stringDataAllocator->GetFreeBlockMemory() >> 10,
stringDataAllocator->GetNumFreeBlocks(), stringDataAllocator->GetNumEmptyBaseBlocks() );
idWStr::ShowMemoryUsage_f( args );
}
/*
============
idStr::SetStringAllocator
============
*/
void idStr::SetStringAllocator( stringDataAllocator_t* allocator ) {
if( !stringAllocatorIsShared ) {
delete stringDataAllocator;
}
stringDataAllocator = allocator;
stringAllocatorIsShared = true;
}
/*
============
idStr::GetStringAllocator
============
*/
stringDataAllocator_t* idStr::GetStringAllocator( void ) {
return stringDataAllocator;
}
/*
===============
idStr::IndentAndPad
adds a formated, indented line to a string. The line is indented "indent"
characters and the formatted string is written. If the string size is less than
the pad size, the remaining characters in the string are filled with spaces up
to the "pad" position.
===============
*/
void idStr::IndentAndPad( int indent, int pad, idStr &str, const char *fmt, ... ) {
assert( pad >= 0 );
if ( pad < 0 ) {
pad = 0;
}
int max = 1024;
char *buff = (char *)_alloca( 1024 + 1 );
memset( buff, 0x20, indent > 128 ? 128 : indent );
va_list argptr;
va_start( argptr, fmt );
vsnPrintf( buff + indent, max - indent, fmt, argptr );
va_end( argptr );
int len = Length( buff );
if ( pad && len <= pad ) {
memset( buff + len, 0x20, pad - len );
buff[ pad ] = '\0';
} else {
// ensure there's at least 1 space of padding if the formatted string
// exceeded the pad size
buff[ len ] = ' ';
buff[ len + 1 ] = '\0';
}
str += buff;
}
/*
===============
idStr::FormatInt
formats integers with commas for readability
===============
*/
const char* idStr::FormatInt( const int num ) {
static idStr val;
val = va( "%d", num );
int len = val.Length();
for ( int i = 0 ; i < ( ( len - 1 ) / 3 ); i++ ) {
int pos = val.Length() - ( ( i + 1 ) * 3 + i );
val.Insert( ',', pos );
}
return ( val.c_str() );
}
/*
============
idStr::EraseRange
============
*/
void idStr::EraseRange( int start, int len ) {
if( IsEmpty() || len == 0 ) {
return;
}
if( start < 0 ) {
start = 0;
}
if( start >= this->len ) {
return;
}
int totalLength = Length();
if( len == INVALID_POSITION ) {
len = totalLength - start;
}
if( len == totalLength ) {
// erase the whole thing
Empty();
return;
}
if( totalLength - start - len ) {
memmove( &data[ start ], &data[ start + len ], totalLength - start - len );
}
data[ totalLength - len ] = '\0';
this->len -= len;
}
/*
============
idStr::EraseChar
============
*/
void idStr::EraseChar( const char c, int start ) {
if( start < 0 ) {
start = 0;
}
int totalLength = Length();
while( start < totalLength - 1 ) {
int offset = start + 1;
while( data[ start ] == c && offset < totalLength ) {
idSwap( data[ start ], data[ offset ] );
offset++;
}
start++;
}
start = totalLength - 1;
while( start > 0 && data[ start ] == c ) {
data[ start ] = '\0';
start--;
}
len = start + 1;
}
/*
============
idStr::Append
============
*/
void idStr::Append( int count, const char c ) {
EnsureAlloced( len + count + 1 );
int start = len;
int end = len + count;
while( start < end ) {
data[ start ] = c;
start++;
}
data[ start ] = '\0';
len += count;
}
/*
============
idStr::StripComments
============
*/
idStr& idStr::StripComments() {
// handle C++-style comments
int startIndex = Find( "//" );
int endIndex = Find( "\n", true, startIndex + 2 );
while( startIndex != -1 && endIndex != -1 ) {
int oldLength = len;
EraseRange( startIndex, endIndex - startIndex );
if( len == oldLength ) {
idLib::common->Warning( "StripCommentsFromString: Couldn't strip comments" );
break; //avoid infinite loops
}
startIndex = Find( "//" );
endIndex = Find( "\n", true, startIndex + 2 );
}
// handle C-style comments
startIndex = Find( "/*" );
endIndex = Find( "*/", true, startIndex + 2 );
if( ( startIndex != -1 && endIndex == -1 ) || ( startIndex == -1 && endIndex != -1 )) {
idLib::common->Warning( "StripCommentsFromString: mismatched /* */ comment" );
return *this;
}
while( startIndex != -1 && endIndex != -1 ) {
int oldLength = len;
EraseRange( startIndex, endIndex - startIndex + 2 );
if( len == oldLength ) {
idLib::common->Warning( "StripCommentsFromString: Couldn't strip comments" );
break; //avoid infinite loops
}
startIndex = Find( "/*" );
endIndex = Find( "*/", true, startIndex + 2 );
}
return *this;
}
/*
============
idStr::Indent
braces within C or C++ style comments are ignored
============
*/
idStr& idStr::Indent() {
Replace( "\r\n", "\n" ); // kill windows line endings
EraseChar( '\r' ); // kill broken windows line endings
// strip out tabs at the beginning of lines
int i;
for( i = 0; i < len; ++i ) {
if( data[ i ] == '\n' ) {
++i;
while( i < len && data[ i ] == '\t' ) {
EraseRange( i, 1 );
}
--i;
}
}
idStr output;
output.EnsureAlloced( len, false );
int indent = 0;
for( i = 0; i < len; i++ ) {
// skip braces within comments
if( i + 1 < len) {
if( data[ i ] == '/' && data[ i + 1 ] == '/' ) {
while( i < len && data[ i ] != '\n' ) {
output += data[ i ];
i++;
}
} else if( data[ i ] == '/' && data[ i + 1 ] == '*' ) {
while( i < len && !( data[ i ] == '*' && data[ i + 1 ] == '/' )) {
output += data[ i ];
i++;
}
}
}
if( data[ i ] == '{' ) {
indent++;
} else if( data[ i ] == '}' ) {
indent--;
// unindent closing braces
output.StripTrailingOnce( "\t" );
}
output += data[ i ];
if( data[ i ] == '\n' && indent > 0 ) {
output.Append( indent, '\t' );
}
}
*this = output;
return *this;
}
/*
============
idStr::Unindent
unindent all lines; tabs are preserved if they are in the middle of a line
============
*/
idStr& idStr::Unindent() {
idStr output;
output.EnsureAlloced( len, false );
int i;
for( i = 0; i < len; ++i ) {
if( data[ i ] == '\t' && i > 0 && data[ i - 1 ] == '\n' ) {
// strip leading tabs
while( i < len && data[ i ] == '\t' ) {
++i;
}
} else if( data[ i ] == '\t' && i > 0 && data[ i - 1 ] != '\n' ) {
// strip trailing tabs
int temp = i;
while( temp < len ) {
if( data[ temp ] == '\r' || data[ temp ] == '\n' ) {
i = temp;
break;
} else if( data[ temp ] != '\t' ){
break;
}
++temp;
}
}
output += data[ i ];
}
*this = output;
return *this;
}
static const char* const hexDigits = "0123456789ABCDEF";
/*
============
idStr::StringToBinaryString
============
*/
void idStr::StringToBinaryString( idStr& out, void *pv, int size ) {
sdAutoPtr< unsigned char, sdArrayCleanupPolicy< unsigned char > > in( new unsigned char[ size ] );
memset( in.Get(), 0, size );
memcpy( in.Get(), pv, size );
for( int i = 0; i < size; i++ ) {
unsigned char c = in[ i ];
out += hexDigits[ c >> 4 ];
out += hexDigits[ c & 0x0f ];
}
}
/*
============
idStr::BinaryStringToString
============
*/
bool idStr::BinaryStringToString( const char* str, void* pv, int size ) {
bool ret = false;
int length = idStr::Length( str );
if ( length / 2 == size ) {
sdAutoPtr< unsigned char, sdArrayCleanupPolicy< unsigned char > > out( new unsigned char[ size ] );
int j = 0;
for ( int i = 0; i < length; i += 2 ) {
char c;
if( str[ i ] > '9' ) {
c = str[ i ] - 'A' + 0x0a;
} else {
c = str[ i ] - 0x30;
}
c <<= 4;
if( str[ i + 1 ] > '9' ) {
c |= str[ i + 1 ] - 'A' + 0x0a;
} else {
c |= str[ i + 1 ] - 0x30;
}
assert( j < ( size ) );
out[ j++ ] = c;
}
memcpy( pv, out.Get(), size );
ret = true;
} else {
assert( !"Invalid size for binary string" );
}
return ret;
}
/*
============
idStr::IsValidEmailAddress
============
*/
bool idStr::IsValidEmailAddress( const char* address ) {
int count = 0;
const char* c;
const char* domain;
static const char* rfc822 = "()<>@,;:\\\"[]";
// validate name
for ( c = address; *c != '\0'; c++ ) {
if ( *c == '\"' && ( c == address || *(c - 1) == '.' || *(c - 1) == '\"' ) ) {
while ( *++c ) {
if ( *c == '\"' ) {
break;
}
if ( *c == '\\' && ( *++c == ' ' ) ) {
continue;
}
if ( *c <= ' ' || *c >= 127 ) {
return 0;
}
}
if ( *c++ == '\0' ) {
return false;
}
if ( *c == '@' ) {
break;
}
if ( *c == '.' ) {
return false;
}
continue;
}
if ( *c == '@' ) {
break;
}
if ( *c <= ' ' || *c >= 127 ) {
return false;
}
if ( FindChar( rfc822, *c ) != INVALID_POSITION ) {
return false;
}
}
if ( c == address || *(c - 1 ) == '.' ) {
return false;
}
// validate domain
if ( *( domain = ++c ) == '\0' ) {
return false;
}
do {
if ( *c == '.' ) {
if ( c == domain || *(c - 1) == '.' ) {
return false;
}
count++;
}
if ( *c <= ' ' || *c >= 127 ) {
return false;
}
if ( FindChar( rfc822, *c ) != INVALID_POSITION ) {
return false;
}
} while ( *++c );
return ( count >= 1 );
}
/*
============
idStr::MS2HMS
============
*/
const char* idStr::MS2HMS( double ms, const hmsFormat_t& formatSpec ) {
if ( ms < 0.0 ) {
ms = 0.0;
}
int sec = idMath::Ftoi( MS2SEC( ms ) );
if( sec == 0 && formatSpec.showZeroSeconds == false ) {
return "";
}
int min = sec / 60;
int hour = min / 60;
sec -= min * 60;
min -= hour * 60;
// don't show minutes if they're zeroed
if( min == 0 && hour == 0 && formatSpec.showZeroMinutes == false && formatSpec.showZeroHours == false ) {
return va( "%02i", sec );
}
// don't show hours if they're zeroed
if( hour == 0 && formatSpec.showZeroHours == false ) {
return va( "%02i:%02i", min, sec );
}
return va( "%02i:%02i:%02i", hour, min, sec );
}
/*
============
idStr::CollapseColors
============
*/
idStr& idStr::CollapseColors( void ) {
int colorBegin = -1;
int lastColor = -1;
for( int i = 0; i < len; i++ ) {
while( idStr::IsColor( &data[ i ] ) && i < len ) {
if( colorBegin == -1 ) {
colorBegin = i;
}
lastColor = i;
i += 2;
}
if( colorBegin != -1 && lastColor != colorBegin ) {
EraseRange( colorBegin, lastColor - colorBegin );
i -= lastColor - colorBegin;
}
colorBegin = -1;
lastColor = -1;
}
return *this;
}