2909 lines
58 KiB
C++
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;
|
|
}
|