2288 lines
53 KiB
C++
2288 lines
53 KiB
C++
#include "global.h"
|
|
#include "RageUtil.h"
|
|
#include "RageMath.h"
|
|
#include "RageLog.h"
|
|
#include "RageFile.h"
|
|
#include "Foreach.h"
|
|
#include "LocalizedString.h"
|
|
#include "LuaManager.h"
|
|
#include <float.h>
|
|
|
|
#include <numeric>
|
|
#include <ctime>
|
|
#include <sstream>
|
|
#include <map>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <math.h>
|
|
|
|
bool HexToBinary(const RString&, RString&);
|
|
|
|
RandomGen g_RandomNumberGenerator;
|
|
|
|
MersenneTwister::MersenneTwister( int iSeed ) : m_iNext(0)
|
|
{
|
|
Reset( iSeed );
|
|
}
|
|
|
|
void MersenneTwister::Reset( int iSeed )
|
|
{
|
|
if( iSeed == 0 )
|
|
iSeed = time(NULL);
|
|
|
|
m_Values[0] = iSeed;
|
|
m_iNext = 0;
|
|
for( int i = 1; i < 624; ++i )
|
|
m_Values[i] = ((69069 * m_Values[i-1]) + 1) & 0xFFFFFFFF;
|
|
|
|
GenerateValues();
|
|
}
|
|
|
|
void MersenneTwister::GenerateValues()
|
|
{
|
|
static const int mask[] = { 0, 0x9908B0DF };
|
|
|
|
for( int i = 0; i < 227; ++i )
|
|
{
|
|
int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF);
|
|
int iNext = (i + 397);
|
|
|
|
m_Values[i] = m_Values[iNext];
|
|
m_Values[i] ^= (iVal >> 1);
|
|
m_Values[i] ^= mask[iVal&1];
|
|
}
|
|
|
|
for( int i = 227; i < 623; ++i )
|
|
{
|
|
int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF);
|
|
int iNext = (i + 397) - 624;
|
|
|
|
m_Values[i] = m_Values[iNext];
|
|
m_Values[i] ^= (iVal >> 1);
|
|
m_Values[i] ^= mask[iVal&1];
|
|
}
|
|
|
|
int iVal = (m_Values[623] & 0x80000000) + (m_Values[0] & 0x7FFFFFFF);
|
|
int iNext = (623 + 397) - 624;
|
|
m_Values[623] = m_Values[iNext] ^ (iVal>>1);
|
|
m_Values[623] ^= mask[iVal&1];
|
|
}
|
|
|
|
int MersenneTwister::Temper( int iVal )
|
|
{
|
|
iVal ^= (iVal >> 11);
|
|
iVal ^= (iVal << 7) & 0x9D2C5680;
|
|
iVal ^= (iVal << 15) & 0xEFC60000;
|
|
iVal ^= (iVal >> 18);
|
|
return iVal;
|
|
}
|
|
|
|
int MersenneTwister::operator()()
|
|
{
|
|
if( m_iNext == 624 )
|
|
{
|
|
m_iNext = 0;
|
|
GenerateValues();
|
|
}
|
|
|
|
return Temper( m_Values[m_iNext++] );
|
|
}
|
|
|
|
void fapproach( float& val, float other_val, float to_move )
|
|
{
|
|
ASSERT_M( to_move >= 0, ssprintf("to_move: %f", to_move) );
|
|
if( val == other_val )
|
|
return;
|
|
float fDelta = other_val - val;
|
|
float fSign = fDelta / fabsf( fDelta );
|
|
float fToMove = fSign*to_move;
|
|
if( fabsf(fToMove) > fabsf(fDelta) )
|
|
fToMove = fDelta; // snap
|
|
val += fToMove;
|
|
}
|
|
|
|
/* Return a positive x mod y. */
|
|
float fmodfp(float x, float y)
|
|
{
|
|
x = fmodf(x, y); /* x is [-y,y] */
|
|
x += y; /* x is [0,y*2] */
|
|
x = fmodf(x, y); /* x is [0,y] */
|
|
return x;
|
|
}
|
|
|
|
int power_of_two( int iInput )
|
|
{
|
|
int iValue = 1;
|
|
|
|
while( iValue < iInput )
|
|
iValue <<= 1;
|
|
|
|
return iValue;
|
|
}
|
|
|
|
bool IsAnInt( const RString &s )
|
|
{
|
|
if( !s.size() )
|
|
return false;
|
|
|
|
for( size_t i=0; i < s.size(); ++i )
|
|
if( s[i] < '0' || s[i] > '9' )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsHexVal( const RString &s )
|
|
{
|
|
if( !s.size() )
|
|
return false;
|
|
|
|
for( size_t i=0; i < s.size(); ++i )
|
|
if( !(s[i] >= '0' && s[i] <= '9') &&
|
|
!(toupper(s[i]) >= 'A' && toupper(s[i]) <= 'F'))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
RString BinaryToHex( const void *pData_, int iNumBytes )
|
|
{
|
|
const unsigned char *pData = (const unsigned char *) pData_;
|
|
RString s;
|
|
for( int i=0; i<iNumBytes; i++ )
|
|
{
|
|
unsigned val = pData[i];
|
|
s += ssprintf( "%02x", val );
|
|
}
|
|
return s;
|
|
}
|
|
|
|
RString BinaryToHex( const RString &sString )
|
|
{
|
|
return BinaryToHex( sString.data(), sString.size() );
|
|
}
|
|
|
|
bool HexToBinary( const RString &s, unsigned char *stringOut )
|
|
{
|
|
if( !IsHexVal(s) )
|
|
return false;
|
|
|
|
for( int i=0; true; i++ )
|
|
{
|
|
if( (int)s.size() <= i*2 )
|
|
break;
|
|
RString sByte = s.substr( i*2, 2 );
|
|
|
|
uint8_t val = 0;
|
|
if( sscanf( sByte, "%hhx", &val ) != 1 )
|
|
return false;
|
|
stringOut[i] = val;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool HexToBinary( const RString &s, RString &sOut )
|
|
{
|
|
sOut.resize(s.size() / 2);
|
|
return HexToBinary(s, (unsigned char *) sOut.data());
|
|
}
|
|
|
|
float HHMMSSToSeconds( const RString &sHHMMSS )
|
|
{
|
|
vector<RString> arrayBits;
|
|
split( sHHMMSS, ":", arrayBits, false );
|
|
|
|
while( arrayBits.size() < 3 )
|
|
arrayBits.insert(arrayBits.begin(), "0" ); // pad missing bits
|
|
|
|
float fSeconds = 0;
|
|
fSeconds += StringToInt( arrayBits[0] ) * 60 * 60;
|
|
fSeconds += StringToInt( arrayBits[1] ) * 60;
|
|
fSeconds += StringToFloat( arrayBits[2] );
|
|
|
|
return fSeconds;
|
|
}
|
|
|
|
RString SecondsToHHMMSS( float fSecs )
|
|
{
|
|
const int iMinsDisplay = (int)fSecs/60;
|
|
const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
|
|
RString sReturn = ssprintf( "%02d:%02d:%02d", iMinsDisplay/60, iMinsDisplay%60, iSecsDisplay );
|
|
return sReturn;
|
|
}
|
|
|
|
RString SecondsToMMSSMsMs( float fSecs )
|
|
{
|
|
const int iMinsDisplay = (int)fSecs/60;
|
|
const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
|
|
const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 );
|
|
RString sReturn = ssprintf( "%02d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) );
|
|
return sReturn;
|
|
}
|
|
|
|
RString SecondsToMSSMsMs( float fSecs )
|
|
{
|
|
const int iMinsDisplay = (int)fSecs/60;
|
|
const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
|
|
const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 );
|
|
RString sReturn = ssprintf( "%01d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) );
|
|
return sReturn;
|
|
}
|
|
|
|
RString SecondsToMMSSMsMsMs( float fSecs )
|
|
{
|
|
const int iMinsDisplay = (int)fSecs/60;
|
|
const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
|
|
const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 1000 );
|
|
RString sReturn = ssprintf( "%02d:%02d.%03d", iMinsDisplay, iSecsDisplay, min(999,iLeftoverDisplay) );
|
|
return sReturn;
|
|
}
|
|
|
|
RString SecondsToMSS( float fSecs )
|
|
{
|
|
const int iMinsDisplay = (int)fSecs/60;
|
|
const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
|
|
RString sReturn = ssprintf( "%01d:%02d", iMinsDisplay, iSecsDisplay);
|
|
return sReturn;
|
|
}
|
|
|
|
RString SecondsToMMSS( float fSecs )
|
|
{
|
|
const int iMinsDisplay = (int)fSecs/60;
|
|
const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
|
|
RString sReturn = ssprintf( "%02d:%02d", iMinsDisplay, iSecsDisplay);
|
|
return sReturn;
|
|
}
|
|
|
|
RString PrettyPercent( float fNumerator, float fDenominator)
|
|
{
|
|
return ssprintf("%0.2f%%",fNumerator/fDenominator*100);
|
|
}
|
|
|
|
RString Commify( int iNum )
|
|
{
|
|
RString sNum = ssprintf("%d",iNum);
|
|
return Commify( sNum );
|
|
}
|
|
|
|
RString Commify( RString sNum, RString sSeperator )
|
|
{
|
|
RString sReturn;
|
|
for( unsigned i=0; i<sNum.length(); i++ )
|
|
{
|
|
char cDigit = sNum[sNum.length()-1-i];
|
|
if( i!=0 && i%3 == 0 )
|
|
sReturn = sSeperator + sReturn;
|
|
sReturn = cDigit + sReturn;
|
|
}
|
|
return sReturn;
|
|
}
|
|
|
|
static LocalizedString NUM_PREFIX ( "RageUtil", "NumPrefix" );
|
|
static LocalizedString NUM_ST ( "RageUtil", "NumSt" );
|
|
static LocalizedString NUM_ND ( "RageUtil", "NumNd" );
|
|
static LocalizedString NUM_RD ( "RageUtil", "NumRd" );
|
|
static LocalizedString NUM_TH ( "RageUtil", "NumTh" );
|
|
RString FormatNumberAndSuffix( int i )
|
|
{
|
|
RString sSuffix;
|
|
switch( i%10 )
|
|
{
|
|
case 1: sSuffix = NUM_ST; break;
|
|
case 2: sSuffix = NUM_ND; break;
|
|
case 3: sSuffix = NUM_RD; break;
|
|
default: sSuffix = NUM_TH; break;
|
|
}
|
|
|
|
// "11th", "113th", etc.
|
|
if( ((i%100) / 10) == 1 )
|
|
sSuffix = NUM_TH;
|
|
|
|
return NUM_PREFIX.GetValue() + ssprintf("%i", i) + sSuffix;
|
|
}
|
|
|
|
struct tm GetLocalTime()
|
|
{
|
|
const time_t t = time(NULL);
|
|
struct tm tm;
|
|
localtime_r( &t, &tm );
|
|
return tm;
|
|
}
|
|
|
|
|
|
RString ssprintf( const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
return vssprintf(fmt, va);
|
|
}
|
|
|
|
#define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try
|
|
|
|
RString vssprintf( const char *szFormat, va_list argList )
|
|
{
|
|
RString sStr;
|
|
|
|
#if defined(WIN32) && !defined(__MINGW32__)
|
|
char *pBuf = NULL;
|
|
int iChars = 1;
|
|
int iUsed = 0;
|
|
int iTry = 0;
|
|
|
|
do
|
|
{
|
|
// Grow more than linearly (e.g. 512, 1536, 3072, etc)
|
|
iChars += iTry * FMT_BLOCK_SIZE;
|
|
pBuf = (char*) _alloca( sizeof(char)*iChars );
|
|
iUsed = vsnprintf( pBuf, iChars-1, szFormat, argList );
|
|
++iTry;
|
|
} while( iUsed < 0 );
|
|
|
|
// assign whatever we managed to format
|
|
sStr.assign( pBuf, iUsed );
|
|
#else
|
|
static bool bExactSizeSupported;
|
|
static bool bInitialized = false;
|
|
if( !bInitialized )
|
|
{
|
|
/* Some systems return the actual size required when snprintf
|
|
* doesn't have enough space. This lets us avoid wasting time
|
|
* iterating, and wasting memory. */
|
|
char ignore;
|
|
bExactSizeSupported = ( snprintf( &ignore, 0, "Hello World" ) == 11 );
|
|
bInitialized = true;
|
|
}
|
|
|
|
if( bExactSizeSupported )
|
|
{
|
|
va_list tmp;
|
|
va_copy( tmp, argList );
|
|
char ignore;
|
|
int iNeeded = vsnprintf( &ignore, 0, szFormat, tmp );
|
|
va_end(tmp);
|
|
|
|
char *buf = sStr.GetBuffer( iNeeded+1 );
|
|
vsnprintf( buf, iNeeded+1, szFormat, argList );
|
|
sStr.ReleaseBuffer( iNeeded );
|
|
return sStr;
|
|
}
|
|
|
|
int iChars = FMT_BLOCK_SIZE;
|
|
int iTry = 1;
|
|
while( 1 )
|
|
{
|
|
// Grow more than linearly (e.g. 512, 1536, 3072, etc)
|
|
char *buf = sStr.GetBuffer(iChars);
|
|
int iUsed = vsnprintf(buf, iChars-1, szFormat, argList);
|
|
|
|
if( iUsed == -1 )
|
|
{
|
|
iChars += ((iTry+1) * FMT_BLOCK_SIZE);
|
|
sStr.ReleaseBuffer();
|
|
++iTry;
|
|
continue;
|
|
}
|
|
|
|
/* OK */
|
|
sStr.ReleaseBuffer(iUsed);
|
|
break;
|
|
}
|
|
#endif
|
|
return sStr;
|
|
}
|
|
|
|
/* Windows uses %I64i to format a 64-bit int, instead of %lli. Convert "a b %lli %-3llu c d"
|
|
* to "a b %I64 %-3I64u c d". This assumes a well-formed format string; invalid format strings
|
|
* should not crash, but the results are undefined. */
|
|
#if defined(WIN32)
|
|
RString ConvertI64FormatString( const RString &sStr )
|
|
{
|
|
RString sRet;
|
|
sRet.reserve( sStr.size() + 16 );
|
|
|
|
size_t iOffset = 0;
|
|
while( iOffset < sStr.size() )
|
|
{
|
|
size_t iPercent = sStr.find( '%', iOffset );
|
|
if( iPercent != sStr.npos )
|
|
{
|
|
sRet.append( sStr, iOffset, iPercent - iOffset );
|
|
iOffset = iPercent;
|
|
}
|
|
|
|
size_t iEnd = sStr.find_first_of( "diouxXeEfFgGaAcsCSpnm%", iOffset + 1 );
|
|
if( iEnd != sStr.npos && iEnd - iPercent >= 3 && iPercent > 2 && sStr[iEnd-2] == 'l' && sStr[iEnd-1] == 'l' )
|
|
{
|
|
sRet.append( sStr, iPercent, iEnd - iPercent - 2 ); // %
|
|
sRet.append( "I64" ); // %I64
|
|
sRet.append( sStr, iEnd, 1 ); // %I64i
|
|
iOffset = iEnd + 1;
|
|
}
|
|
else
|
|
{
|
|
if( iEnd == sStr.npos )
|
|
iEnd = sStr.size() - 1;
|
|
sRet.append( sStr, iOffset, iEnd - iOffset + 1 );
|
|
iOffset = iEnd + 1;
|
|
}
|
|
}
|
|
return sRet;
|
|
}
|
|
#else
|
|
RString ConvertI64FormatString( const RString &sStr ) { return sStr; }
|
|
#endif
|
|
|
|
/* ISO-639-1 codes: http://www.loc.gov/standards/iso639-2/langcodes.html
|
|
* native forms: http://people.w3.org/rishida/names/languages.html
|
|
* We don't use 3-letter codes, so we don't bother supporting them. */
|
|
static const LanguageInfo g_langs[] =
|
|
{
|
|
{"aa", "Afar"},
|
|
{"ab", "Abkhazian"},
|
|
{"af", "Afrikaans"},
|
|
{"am", "Amharic"},
|
|
{"ar", "Arabic"},
|
|
{"as", "Assamese"},
|
|
{"ay", "Aymara"},
|
|
{"az", "Azerbaijani"},
|
|
{"ba", "Bashkir"},
|
|
{"be", "Byelorussian"},
|
|
{"bg", "Bulgarian"},
|
|
{"bh", "Bihari"},
|
|
{"bi", "Bislama"},
|
|
{"bn", "Bengali"},
|
|
{"bo", "Tibetan"},
|
|
{"br", "Breton"},
|
|
{"ca", "Catalan"},
|
|
{"co", "Corsican"},
|
|
{"cs", "Czech"},
|
|
{"cy", "Welsh"},
|
|
{"da", "Danish"},
|
|
{"de", "German"},
|
|
{"dz", "Bhutani"},
|
|
{"el", "Greek"},
|
|
{"en", "English"},
|
|
{"eo", "Esperanto"},
|
|
{"es", "Spanish"},
|
|
{"et", "Estonian"},
|
|
{"eu", "Basque"},
|
|
{"fa", "Persian"},
|
|
{"fi", "Finnish"},
|
|
{"fj", "Fiji"},
|
|
{"fo", "Faeroese"},
|
|
{"fr", "French"},
|
|
{"fy", "Frisian"},
|
|
{"ga", "Irish"},
|
|
{"gd", "Gaelic"},
|
|
{"gl", "Galician"},
|
|
{"gn", "Guarani"},
|
|
{"gu", "Gujarati"},
|
|
{"ha", "Hausa"},
|
|
{"he", "Hebrew"},
|
|
{"hi", "Hindi"},
|
|
{"hr", "Croatian"},
|
|
{"hu", "Hungarian"},
|
|
{"hy", "Armenian"},
|
|
{"ia", "Interlingua"},
|
|
{"id", "Indonesian"},
|
|
{"ie", "Interlingue"},
|
|
{"ik", "Inupiak"},
|
|
{"in", "Indonesian"}, // compatibility
|
|
{"is", "Icelandic"},
|
|
{"it", "Italian"},
|
|
{"iw", "Hebrew"}, // compatibility
|
|
{"ja", "Japanese"},
|
|
{"ji", "Yiddish"}, // compatibility
|
|
{"jw", "Javanese"},
|
|
{"ka", "Georgian"},
|
|
{"kk", "Kazakh"},
|
|
{"kl", "Greenlandic"},
|
|
{"km", "Cambodian"},
|
|
{"kn", "Kannada"},
|
|
{"ko", "Korean"},
|
|
{"ks", "Kashmiri"},
|
|
{"ku", "Kurdish"},
|
|
{"ky", "Kirghiz"},
|
|
{"la", "Latin"},
|
|
{"ln", "Lingala"},
|
|
{"lo", "Laothian"},
|
|
{"lt", "Lithuanian"},
|
|
{"lv", "Latvian"},
|
|
{"mg", "Malagasy"},
|
|
{"mi", "Maori"},
|
|
{"mk", "Macedonian"},
|
|
{"ml", "Malayalam"},
|
|
{"mn", "Mongolian"},
|
|
{"mo", "Moldavian"},
|
|
{"mr", "Marathi"},
|
|
{"ms", "Malay"},
|
|
{"mt", "Maltese"},
|
|
{"my", "Burmese"},
|
|
{"na", "Nauru"},
|
|
{"ne", "Nepali"},
|
|
{"nl", "Dutch"},
|
|
{"no", "Norwegian"},
|
|
{"oc", "Occitan"},
|
|
{"om", "Oromo"},
|
|
{"or", "Oriya"},
|
|
{"pa", "Punjabi"},
|
|
{"pl", "Polish"},
|
|
{"ps", "Pashto"},
|
|
{"pt", "Portuguese"},
|
|
{"qu", "Quechua"},
|
|
{"rm", "Rhaeto-Romance"},
|
|
{"rn", "Kirundi"},
|
|
{"ro", "Romanian"},
|
|
{"ru", "Russian"},
|
|
{"rw", "Kinyarwanda"},
|
|
{"sa", "Sanskrit"},
|
|
{"sd", "Sindhi"},
|
|
{"sg", "Sangro"},
|
|
{"sh", "Serbo-Croatian"},
|
|
{"si", "Singhalese"},
|
|
{"sk", "Slovak"},
|
|
{"sl", "Slovenian"},
|
|
{"sm", "Samoan"},
|
|
{"sn", "Shona"},
|
|
{"so", "Somali"},
|
|
{"sq", "Albanian"},
|
|
{"sr", "Serbian"},
|
|
{"ss", "Siswati"},
|
|
{"st", "Sesotho"},
|
|
{"su", "Sudanese"},
|
|
{"sv", "Swedish"},
|
|
{"sw", "Swahili"},
|
|
{"ta", "Tamil"},
|
|
{"te", "Tegulu"},
|
|
{"tg", "Tajik"},
|
|
{"th", "Thai"},
|
|
{"ti", "Tigrinya"},
|
|
{"tk", "Turkmen"},
|
|
{"tl", "Tagalog"},
|
|
{"tn", "Setswana"},
|
|
{"to", "Tonga"},
|
|
{"tr", "Turkish"},
|
|
{"ts", "Tsonga"},
|
|
{"tt", "Tatar"},
|
|
{"tw", "Twi"},
|
|
{"uk", "Ukrainian"},
|
|
{"ur", "Urdu"},
|
|
{"uz", "Uzbek"},
|
|
{"vi", "Vietnamese"},
|
|
{"vo", "Volapuk"},
|
|
{"wo", "Wolof"},
|
|
{"xh", "Xhosa"},
|
|
{"yi", "Yiddish"},
|
|
{"yo", "Yoruba"},
|
|
{"zh", "Chinese"},
|
|
{"zu", "Zulu"},
|
|
};
|
|
|
|
void GetLanguageInfos( vector<const LanguageInfo*> &vAddTo )
|
|
{
|
|
for( unsigned i=0; i<ARRAYLEN(g_langs); ++i )
|
|
vAddTo.push_back( &g_langs[i] );
|
|
}
|
|
|
|
const LanguageInfo *GetLanguageInfo( const RString &sIsoCode )
|
|
{
|
|
for( unsigned i=0; i<ARRAYLEN(g_langs); ++i )
|
|
{
|
|
if( sIsoCode.EqualsNoCase(g_langs[i].szIsoCode) )
|
|
return &g_langs[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
RString join( const RString &sDeliminator, const vector<RString> &sSource)
|
|
{
|
|
if( sSource.empty() )
|
|
return RString();
|
|
|
|
RString sTmp;
|
|
|
|
for( unsigned iNum = 0; iNum < sSource.size()-1; iNum++ )
|
|
{
|
|
sTmp += sSource[iNum];
|
|
sTmp += sDeliminator;
|
|
}
|
|
sTmp += sSource.back();
|
|
return sTmp;
|
|
}
|
|
|
|
RString join( const RString &sDelimitor, vector<RString>::const_iterator begin, vector<RString>::const_iterator end )
|
|
{
|
|
if( begin == end )
|
|
return RString();
|
|
|
|
RString sRet;
|
|
while( begin != end )
|
|
{
|
|
sRet += *begin;
|
|
++begin;
|
|
if( begin != end )
|
|
sRet += sDelimitor;
|
|
}
|
|
|
|
return sRet;
|
|
}
|
|
|
|
RString SmEscape( const RString &sUnescaped )
|
|
{
|
|
return SmEscape( sUnescaped.c_str(), sUnescaped.size() );
|
|
}
|
|
|
|
RString SmEscape( const char *cUnescaped, int len )
|
|
{
|
|
RString answer = "";
|
|
for( int i = 0; i < len; ++i )
|
|
{
|
|
// Other characters we could theoretically escape:
|
|
// NotesWriterSM.cpp used to claim ',' should be escaped, but there was no explanation why
|
|
// '#' is both a control character and a valid part of a parameter. The only way for there to be
|
|
// any confusion is in a misformatted .sm file, though, so it is unnecessary to escape it.
|
|
if( cUnescaped[i] == '/' && i + 1 < len && cUnescaped[i + 1] == '/' )
|
|
{
|
|
answer += "\\/\\/";
|
|
++i; // increment here so we skip both //s
|
|
continue;
|
|
}
|
|
if( cUnescaped[i] == '\\' || cUnescaped[i] == ':' || cUnescaped[i] == ';' )
|
|
answer += "\\";
|
|
answer += cUnescaped[i];
|
|
}
|
|
return answer;
|
|
}
|
|
|
|
RString DwiEscape( const RString &sUnescaped )
|
|
{
|
|
return DwiEscape( sUnescaped.c_str(), sUnescaped.size() );
|
|
}
|
|
|
|
RString DwiEscape( const char *cUnescaped, int len )
|
|
{
|
|
RString answer = "";
|
|
for( int i = 0; i < len; ++i )
|
|
{
|
|
switch( cUnescaped[i] )
|
|
{
|
|
// TODO: Which of these characters actually affect DWI?
|
|
case '\\':
|
|
case ':':
|
|
case ';': answer += '|'; break;
|
|
case '[': answer += '('; break;
|
|
case ']': answer += ')'; break;
|
|
default: answer += cUnescaped[i];
|
|
}
|
|
}
|
|
return answer;
|
|
}
|
|
|
|
|
|
template <class S>
|
|
static int DelimitorLength( const S &Delimitor )
|
|
{
|
|
return Delimitor.size();
|
|
}
|
|
|
|
static int DelimitorLength( char Delimitor )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static int DelimitorLength( wchar_t Delimitor )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
template <class S, class C>
|
|
void do_split( const S &Source, const C Delimitor, vector<S> &AddIt, const bool bIgnoreEmpty )
|
|
{
|
|
/* Short-circuit if the source is empty; we want to return an empty vector if
|
|
* the string is empty, even if bIgnoreEmpty is true. */
|
|
if( Source.empty() )
|
|
return;
|
|
|
|
size_t startpos = 0;
|
|
|
|
do {
|
|
size_t pos;
|
|
pos = Source.find( Delimitor, startpos );
|
|
if( pos == Source.npos )
|
|
pos = Source.size();
|
|
|
|
if( pos-startpos > 0 || !bIgnoreEmpty )
|
|
{
|
|
/* Optimization: if we're copying the whole string, avoid substr; this
|
|
* allows this copy to be refcounted, which is much faster. */
|
|
if( startpos == 0 && pos-startpos == Source.size() )
|
|
AddIt.push_back(Source);
|
|
else
|
|
{
|
|
const S AddRString = Source.substr(startpos, pos-startpos);
|
|
AddIt.push_back(AddRString);
|
|
}
|
|
}
|
|
|
|
startpos = pos+DelimitorLength(Delimitor);
|
|
} while ( startpos <= Source.size() );
|
|
}
|
|
|
|
|
|
void split( const RString &sSource, const RString &sDelimitor, vector<RString> &asAddIt, const bool bIgnoreEmpty )
|
|
{
|
|
if( sDelimitor.size() == 1 )
|
|
do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty );
|
|
else
|
|
do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty );
|
|
}
|
|
|
|
void split( const wstring &sSource, const wstring &sDelimitor, vector<wstring> &asAddIt, const bool bIgnoreEmpty )
|
|
{
|
|
if( sDelimitor.size() == 1 )
|
|
do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty );
|
|
else
|
|
do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty );
|
|
}
|
|
|
|
/* Use:
|
|
|
|
RString str="a,b,c";
|
|
int start = 0, size = -1;
|
|
while( 1 )
|
|
{
|
|
do_split( str, ",", start, size );
|
|
if( start == str.size() )
|
|
break;
|
|
str[start] = 'Q';
|
|
}
|
|
|
|
*/
|
|
|
|
template <class S>
|
|
void do_split( const S &Source, const S &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
|
|
{
|
|
if( size != -1 )
|
|
{
|
|
/* Start points to the beginning of the last delimiter. Move it up. */
|
|
begin += size+Delimitor.size();
|
|
begin = min( begin, len );
|
|
}
|
|
|
|
size = 0;
|
|
|
|
if( bIgnoreEmpty )
|
|
{
|
|
/* Skip delims. */
|
|
while( begin + Delimitor.size() < Source.size() &&
|
|
!Source.compare( begin, Delimitor.size(), Delimitor ) )
|
|
++begin;
|
|
}
|
|
|
|
/* Where's the string function to find within a substring? C++ strings apparently
|
|
* are missing that ... */
|
|
size_t pos;
|
|
if( Delimitor.size() == 1 )
|
|
pos = Source.find( Delimitor[0], begin );
|
|
else
|
|
pos = Source.find( Delimitor, begin );
|
|
if( pos == Source.npos || (int) pos > len )
|
|
pos = len;
|
|
size = pos - begin;
|
|
}
|
|
|
|
void split( const RString &Source, const RString &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
|
|
{
|
|
do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty );
|
|
|
|
}
|
|
|
|
void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
|
|
{
|
|
do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty );
|
|
}
|
|
|
|
void split( const RString &Source, const RString &Delimitor, int &begin, int &size, const bool bIgnoreEmpty )
|
|
{
|
|
do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty );
|
|
}
|
|
|
|
void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, const bool bIgnoreEmpty )
|
|
{
|
|
do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* foo\fum\ -> "foo\fum\", "", ""
|
|
* c:\foo\bar.txt -> "c:\foo\", "bar", ".txt"
|
|
* \\foo\fum -> "\\foo\", "fum", ""
|
|
*/
|
|
void splitpath( const RString &sPath, RString &sDir, RString &sFilename, RString &sExt )
|
|
{
|
|
sDir = sFilename = sExt = "";
|
|
|
|
vector<RString> asMatches;
|
|
|
|
/*
|
|
* One level of escapes for the regex, one for C. Ew.
|
|
* This is really:
|
|
* ^(.*[\\/])?(.*)$
|
|
*/
|
|
static Regex sep("^(.*[\\\\/])?(.*)$");
|
|
bool bCheck = sep.Compare( sPath, asMatches );
|
|
ASSERT( bCheck );
|
|
|
|
sDir = asMatches[0];
|
|
const RString sBase = asMatches[1];
|
|
|
|
/* ^(.*)(\.[^\.]+)$ */
|
|
static Regex SplitExt("^(.*)(\\.[^\\.]+)$");
|
|
if( SplitExt.Compare(sBase, asMatches) )
|
|
{
|
|
sFilename = asMatches[0];
|
|
sExt = asMatches[1];
|
|
}
|
|
else
|
|
{
|
|
sFilename = sBase;
|
|
}
|
|
}
|
|
|
|
|
|
/* "foo.bar", "baz" -> "foo.baz"
|
|
* "foo", "baz" -> "foo.baz"
|
|
* "foo.bar", "" -> "foo" */
|
|
RString SetExtension( const RString &sPath, const RString &sExt )
|
|
{
|
|
RString sDir, sFileName, sOldExt;
|
|
splitpath( sPath, sDir, sFileName, sOldExt );
|
|
return sDir + sFileName + (sExt.size()? ".":"") + sExt;
|
|
}
|
|
|
|
RString GetExtension( const RString &sPath )
|
|
{
|
|
size_t pos = sPath.rfind( '.' );
|
|
if( pos == sPath.npos )
|
|
return RString();
|
|
|
|
size_t slash = sPath.find( '/', pos );
|
|
if( slash != sPath.npos )
|
|
return RString(); /* rare: path/dir.ext/fn */
|
|
|
|
return sPath.substr( pos+1, sPath.size()-pos+1 );
|
|
}
|
|
|
|
RString GetFileNameWithoutExtension( const RString &sPath )
|
|
{
|
|
RString sThrowAway, sFName;
|
|
splitpath( sPath, sThrowAway, sFName, sThrowAway );
|
|
return sFName;
|
|
}
|
|
|
|
void MakeValidFilename( RString &sName )
|
|
{
|
|
wstring wsName = RStringToWstring( sName );
|
|
wstring wsInvalid = L"/\\:*?\"<>|";
|
|
for( unsigned i = 0; i < wsName.size(); ++i )
|
|
{
|
|
wchar_t w = wsName[i];
|
|
if( w >= 32 &&
|
|
w < 126 &&
|
|
wsInvalid.find_first_of(w) == wsInvalid.npos )
|
|
continue;
|
|
|
|
if( w == L'"' )
|
|
{
|
|
wsName[i] = L'\'';
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We could replace with closest matches in ASCII: convert the character to UTF-8
|
|
* NFD (decomposed) (maybe NFKD?), and see if the first character is ASCII.
|
|
*
|
|
* This is useless for non-Western languages, since we'll replace the whole filename.
|
|
*/
|
|
wsName[i] = '_';
|
|
}
|
|
|
|
sName = WStringToRString( wsName );
|
|
}
|
|
|
|
int g_argc = 0;
|
|
char **g_argv = NULL;
|
|
|
|
void SetCommandlineArguments( int argc, char **argv )
|
|
{
|
|
g_argc = argc;
|
|
g_argv = argv;
|
|
}
|
|
|
|
void GetCommandLineArguments( int &argc, char **&argv )
|
|
{
|
|
argc = g_argc;
|
|
argv = g_argv;
|
|
}
|
|
|
|
/*
|
|
* Search for the commandline argument given; eg. "test" searches for the
|
|
* option "--test". All commandline arguments are getopt_long style: --foo;
|
|
* short arguments (-x) are not supported. (These are not intended for
|
|
* common, general use, so having short options isn't currently needed.)
|
|
* If argument is non-NULL, accept an argument.
|
|
*/
|
|
bool GetCommandlineArgument( const RString &option, RString *argument, int iIndex )
|
|
{
|
|
const RString optstr = "--" + option;
|
|
|
|
for( int arg = 1; arg < g_argc; ++arg )
|
|
{
|
|
const RString CurArgument = g_argv[arg];
|
|
|
|
const size_t i = CurArgument.find( "=" );
|
|
RString CurOption = CurArgument.substr(0,i);
|
|
if( CurOption.CompareNoCase(optstr) )
|
|
continue; /* no match */
|
|
|
|
/* Found it. */
|
|
if( iIndex )
|
|
{
|
|
--iIndex;
|
|
continue;
|
|
}
|
|
|
|
if( argument )
|
|
{
|
|
if( i != RString::npos )
|
|
*argument = CurArgument.substr( i+1 );
|
|
else
|
|
*argument = "";
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
RString GetCwd()
|
|
{
|
|
char buf[PATH_MAX];
|
|
bool ret = getcwd(buf, PATH_MAX) != NULL;
|
|
ASSERT(ret);
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Calculate a standard CRC32. iCRC should be initialized to 0.
|
|
* References:
|
|
* http://www.theorem.com/java/CRC32.java,
|
|
* http://www.faqs.org/rfcs/rfc1952.html
|
|
*/
|
|
void CRC32( unsigned int &iCRC, const void *pVoidBuffer, size_t iSize )
|
|
{
|
|
static unsigned tab[256];
|
|
static bool initted = false;
|
|
if( !initted )
|
|
{
|
|
initted = true;
|
|
const unsigned POLY = 0xEDB88320;
|
|
|
|
for( int i = 0; i < 256; ++i )
|
|
{
|
|
tab[i] = i;
|
|
for( int j = 0; j < 8; ++j )
|
|
{
|
|
if( tab[i] & 1 )
|
|
tab[i] = (tab[i] >> 1) ^ POLY;
|
|
else
|
|
tab[i] >>= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
iCRC ^= 0xFFFFFFFF;
|
|
|
|
const char *pBuffer = (const char *) pVoidBuffer;
|
|
for( unsigned i = 0; i < iSize; ++i )
|
|
iCRC = (iCRC >> 8) ^ tab[(iCRC ^ pBuffer[i]) & 0xFF];
|
|
|
|
iCRC ^= 0xFFFFFFFF;
|
|
}
|
|
|
|
unsigned int GetHashForString ( const RString &s )
|
|
{
|
|
unsigned crc = 0;
|
|
CRC32( crc, s.data(), s.size() );
|
|
return crc;
|
|
}
|
|
|
|
|
|
/* Return true if "dir" is empty or does not exist. */
|
|
bool DirectoryIsEmpty( const RString &sDir )
|
|
{
|
|
if( sDir.empty() )
|
|
return true;
|
|
if( !DoesFileExist(sDir) )
|
|
return true;
|
|
|
|
vector<RString> asFileNames;
|
|
GetDirListing( sDir, asFileNames );
|
|
return asFileNames.empty();
|
|
}
|
|
|
|
bool CompareRStringsAsc( const RString &sStr1, const RString &sStr2 )
|
|
{
|
|
return sStr1.CompareNoCase( sStr2 ) < 0;
|
|
}
|
|
|
|
bool CompareRStringsDesc( const RString &sStr1, const RString &sStr2 )
|
|
{
|
|
return sStr1.CompareNoCase( sStr2 ) > 0;
|
|
}
|
|
|
|
void SortRStringArray( vector<RString> &arrayRStrings, const bool bSortAscending )
|
|
{
|
|
sort( arrayRStrings.begin(), arrayRStrings.end(),
|
|
bSortAscending?CompareRStringsAsc:CompareRStringsDesc );
|
|
}
|
|
|
|
float calc_mean( const float *pStart, const float *pEnd )
|
|
{
|
|
return accumulate( pStart, pEnd, 0.f ) / distance( pStart, pEnd );
|
|
}
|
|
|
|
float calc_stddev( const float *pStart, const float *pEnd, bool bSample )
|
|
{
|
|
/* Calculate the mean. */
|
|
float fMean = calc_mean( pStart, pEnd );
|
|
|
|
/* Calculate stddev. */
|
|
float fDev = 0.0f;
|
|
for( const float *i=pStart; i != pEnd; ++i )
|
|
fDev += (*i - fMean) * (*i - fMean);
|
|
fDev /= distance( pStart, pEnd ) - (bSample ? 1 : 0);
|
|
fDev = sqrtf( fDev );
|
|
|
|
return fDev;
|
|
}
|
|
|
|
bool CalcLeastSquares( const vector< pair<float, float> > &vCoordinates,
|
|
float &fSlope, float &fIntercept, float &fError )
|
|
{
|
|
if( vCoordinates.empty() )
|
|
return false;
|
|
float fSumXX = 0.0f, fSumXY = 0.0f, fSumX = 0.0f, fSumY = 0.0f;
|
|
for( unsigned i = 0; i < vCoordinates.size(); ++i )
|
|
{
|
|
fSumXX += vCoordinates[i].first * vCoordinates[i].first;
|
|
fSumXY += vCoordinates[i].first * vCoordinates[i].second;
|
|
fSumX += vCoordinates[i].first;
|
|
fSumY += vCoordinates[i].second;
|
|
}
|
|
const float fDenominator = vCoordinates.size() * fSumXX - fSumX * fSumX;
|
|
fSlope = (vCoordinates.size() * fSumXY - fSumX * fSumY) / fDenominator;
|
|
fIntercept = (fSumXX * fSumY - fSumX * fSumXY) / fDenominator;
|
|
|
|
fError = 0.0f;
|
|
for( unsigned i = 0; i < vCoordinates.size(); ++i )
|
|
{
|
|
const float fOneError = fIntercept + fSlope * vCoordinates[i].first - vCoordinates[i].second;
|
|
fError += fOneError * fOneError;
|
|
}
|
|
fError /= vCoordinates.size();
|
|
fError = sqrtf( fError );
|
|
return true;
|
|
}
|
|
|
|
void FilterHighErrorPoints( vector< pair<float, float> > &vCoordinates,
|
|
float fSlope, float fIntercept, float fCutoff )
|
|
{
|
|
unsigned int iOut = 0;
|
|
for( unsigned int iIn = 0; iIn < vCoordinates.size(); ++iIn )
|
|
{
|
|
const float fError = fIntercept + fSlope * vCoordinates[iIn].first - vCoordinates[iIn].second;
|
|
if( fabsf(fError) < fCutoff )
|
|
{
|
|
vCoordinates[iOut] = vCoordinates[iIn];
|
|
++iOut;
|
|
}
|
|
}
|
|
vCoordinates.resize( iOut );
|
|
}
|
|
|
|
void TrimLeft( RString &sStr, const char *s )
|
|
{
|
|
int n = 0;
|
|
while( n < int(sStr.size()) && strchr(s, sStr[n]) )
|
|
n++;
|
|
|
|
sStr.erase( sStr.begin(), sStr.begin()+n );
|
|
}
|
|
|
|
void TrimRight( RString &sStr, const char *s )
|
|
{
|
|
int n = sStr.size();
|
|
while( n > 0 && strchr(s, sStr[n-1]) )
|
|
n--;
|
|
|
|
/* Delete from n to the end. If n == sStr.size(), nothing is deleted;
|
|
* if n == 0, the whole string is erased. */
|
|
sStr.erase( sStr.begin()+n, sStr.end() );
|
|
}
|
|
|
|
void Trim( RString &sStr, const char *s )
|
|
{
|
|
RString::size_type b = 0, e = sStr.size();
|
|
while( b < e && strchr(s, sStr[b]) )
|
|
++b;
|
|
while( b < e && strchr(s, sStr[e-1]) )
|
|
--e;
|
|
sStr.assign( sStr.substr(b, e-b) );
|
|
}
|
|
|
|
void StripCrnl( RString &s )
|
|
{
|
|
while( s.size() && (s[s.size()-1] == '\r' || s[s.size()-1] == '\n') )
|
|
s.erase( s.size()-1 );
|
|
}
|
|
|
|
bool BeginsWith( const RString &sTestThis, const RString &sBeginning )
|
|
{
|
|
ASSERT( !sBeginning.empty() );
|
|
return sTestThis.compare( 0, sBeginning.length(), sBeginning ) == 0;
|
|
}
|
|
|
|
bool EndsWith( const RString &sTestThis, const RString &sEnding )
|
|
{
|
|
ASSERT( !sEnding.empty() );
|
|
if( sTestThis.size() < sEnding.size() )
|
|
return false;
|
|
return sTestThis.compare( sTestThis.length()-sEnding.length(), sEnding.length(), sEnding ) == 0;
|
|
}
|
|
|
|
RString URLEncode( const RString &sStr )
|
|
{
|
|
RString sOutput;
|
|
for( unsigned k = 0; k < sStr.size(); k++ )
|
|
{
|
|
char t = sStr[k];
|
|
if( t >= '!' && t <= 'z' )
|
|
sOutput += t;
|
|
else
|
|
sOutput += "%" + ssprintf( "%02X", t );
|
|
}
|
|
return sOutput;
|
|
}
|
|
|
|
static bool CVSOrSVN( const RString& s )
|
|
{
|
|
return s.Right(3).EqualsNoCase("CVS") || s.Right(4) == ".svn";
|
|
}
|
|
|
|
void StripCvsAndSvn( vector<RString> &vs )
|
|
{
|
|
RemoveIf( vs, CVSOrSVN );
|
|
}
|
|
|
|
static bool MacResourceFork( const RString& s )
|
|
{
|
|
return s.Left(2).EqualsNoCase("._");
|
|
}
|
|
|
|
void StripMacResourceForks( vector<RString> &vs )
|
|
{
|
|
RemoveIf( vs, MacResourceFork );
|
|
}
|
|
|
|
/* path is a .redir pathname. Read it and return the real one. */
|
|
RString DerefRedir( const RString &_path )
|
|
{
|
|
RString sPath = _path;
|
|
|
|
for( int i=0; i<100; i++ )
|
|
{
|
|
if( GetExtension(sPath) != "redir" )
|
|
return sPath;
|
|
|
|
RString sNewFileName;
|
|
GetFileContents( sPath, sNewFileName, true );
|
|
|
|
/* Empty is invalid. */
|
|
if( sNewFileName == "" )
|
|
return RString();
|
|
|
|
RString sPath2 = Dirname(sPath) + sNewFileName;
|
|
|
|
CollapsePath( sPath2 );
|
|
|
|
sPath2 += "*";
|
|
|
|
vector<RString> matches;
|
|
GetDirListing( sPath2, matches, false, true );
|
|
|
|
if( matches.empty() )
|
|
RageException::Throw( "The redirect \"%s\" references a file \"%s\" which doesn't exist.", sPath.c_str(), sPath2.c_str() );
|
|
else if( matches.size() > 1 )
|
|
RageException::Throw( "The redirect \"%s\" references a file \"%s\" with multiple matches.", sPath.c_str(), sPath2.c_str() );
|
|
|
|
sPath = matches[0];
|
|
}
|
|
|
|
RageException::Throw( "Circular redirect \"%s\".", sPath.c_str() );
|
|
}
|
|
|
|
bool GetFileContents( const RString &sPath, RString &sOut, bool bOneLine )
|
|
{
|
|
/* Don't warn if the file doesn't exist, but do warn if it exists and fails to open. */
|
|
if( !IsAFile(sPath) )
|
|
return false;
|
|
|
|
RageFile file;
|
|
if( !file.Open(sPath) )
|
|
{
|
|
LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() );
|
|
return false;
|
|
}
|
|
|
|
RString sData;
|
|
int iGot;
|
|
if( bOneLine )
|
|
iGot = file.GetLine( sData );
|
|
else
|
|
iGot = file.Read( sData, file.GetFileSize() );
|
|
|
|
if( iGot == -1 )
|
|
{
|
|
LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() );
|
|
return false;
|
|
}
|
|
|
|
if( bOneLine )
|
|
StripCrnl( sData );
|
|
|
|
sOut = sData;
|
|
return true;
|
|
}
|
|
|
|
bool GetFileContents( const RString &sFile, vector<RString> &asOut )
|
|
{
|
|
RageFile file;
|
|
if( !file.Open(sFile) )
|
|
{
|
|
LOG->Warn( "GetFileContents(%s): %s", sFile.c_str(), file.GetError().c_str() );
|
|
return false;
|
|
}
|
|
|
|
RString sLine;
|
|
while( file.GetLine(sLine) )
|
|
asOut.push_back( sLine );
|
|
return true;
|
|
}
|
|
|
|
#include "../extern/pcre/pcre.h"
|
|
void Regex::Compile()
|
|
{
|
|
const char *error;
|
|
int offset;
|
|
m_pReg = pcre_compile( m_sPattern.c_str(), PCRE_CASELESS, &error, &offset, NULL );
|
|
|
|
if( m_pReg == NULL )
|
|
RageException::Throw( "Invalid regex: \"%s\" (%s).", m_sPattern.c_str(), error );
|
|
|
|
int iRet = pcre_fullinfo( (pcre *) m_pReg, NULL, PCRE_INFO_CAPTURECOUNT, &m_iBackrefs );
|
|
ASSERT( iRet >= 0 );
|
|
|
|
++m_iBackrefs;
|
|
ASSERT( m_iBackrefs < 128 );
|
|
}
|
|
|
|
void Regex::Set( const RString &sStr )
|
|
{
|
|
Release();
|
|
m_sPattern = sStr;
|
|
Compile();
|
|
}
|
|
|
|
void Regex::Release()
|
|
{
|
|
pcre_free( m_pReg );
|
|
m_pReg = NULL;
|
|
m_sPattern = RString();
|
|
}
|
|
|
|
Regex::Regex( const RString &sStr ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString())
|
|
{
|
|
Set( sStr );
|
|
}
|
|
|
|
Regex::Regex( const Regex &rhs ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString())
|
|
{
|
|
Set( rhs.m_sPattern );
|
|
}
|
|
|
|
Regex &Regex::operator=( const Regex &rhs )
|
|
{
|
|
if( this != &rhs )
|
|
Set( rhs.m_sPattern );
|
|
return *this;
|
|
}
|
|
|
|
Regex::~Regex()
|
|
{
|
|
Release();
|
|
}
|
|
|
|
bool Regex::Compare( const RString &sStr )
|
|
{
|
|
int iMat[128*3];
|
|
int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 );
|
|
|
|
if( iRet < -1 )
|
|
RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet );
|
|
|
|
return iRet >= 0;
|
|
}
|
|
|
|
bool Regex::Compare( const RString &sStr, vector<RString> &asMatches )
|
|
{
|
|
asMatches.clear();
|
|
|
|
int iMat[128*3];
|
|
int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 );
|
|
|
|
if( iRet < -1 )
|
|
RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet );
|
|
|
|
if( iRet == -1 )
|
|
return false;
|
|
|
|
for( unsigned i = 1; i < m_iBackrefs; ++i )
|
|
{
|
|
const int iStart = iMat[i*2], end = iMat[i*2+1];
|
|
if( iStart == -1 )
|
|
asMatches.push_back( RString() ); /* no match */
|
|
else
|
|
asMatches.push_back( sStr.substr(iStart, end - iStart) );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Arguments and behavior are the same are similar to
|
|
// http://us3.php.net/manual/en/function.preg-replace.php
|
|
bool Regex::Replace( const RString &sReplacement, const RString &sSubject, RString &sOut )
|
|
{
|
|
vector<RString> asMatches;
|
|
if( !Compare(sSubject, asMatches) )
|
|
return false;
|
|
|
|
sOut = sReplacement;
|
|
|
|
// TODO: optimize me by iterating only once over the string
|
|
for( unsigned i=0; i<asMatches.size(); i++ )
|
|
{
|
|
RString sFrom = ssprintf( "\\${%d}", i );
|
|
RString sTo = asMatches[i];
|
|
sOut.Replace(sFrom, sTo);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Given a UTF-8 byte, return the length of the codepoint (if a start code)
|
|
* or 0 if it's a continuation byte. */
|
|
int utf8_get_char_len( char p )
|
|
{
|
|
if( !(p & 0x80) ) return 1; /* 0xxxxxxx - 1 */
|
|
if( !(p & 0x40) ) return 1; /* 10xxxxxx - continuation */
|
|
if( !(p & 0x20) ) return 2; /* 110xxxxx */
|
|
if( !(p & 0x10) ) return 3; /* 1110xxxx */
|
|
if( !(p & 0x08) ) return 4; /* 11110xxx */
|
|
if( !(p & 0x04) ) return 5; /* 111110xx */
|
|
if( !(p & 0x02) ) return 6; /* 1111110x */
|
|
return 1; /* 1111111x */
|
|
}
|
|
|
|
static inline bool is_utf8_continuation_byte( char c )
|
|
{
|
|
return (c & 0xC0) == 0x80;
|
|
}
|
|
|
|
/* Decode one codepoint at start; advance start and place the result in ch. If
|
|
* the encoded string is invalid, false is returned. */
|
|
bool utf8_to_wchar_ec( const RString &s, unsigned &start, wchar_t &ch )
|
|
{
|
|
if( start >= s.size() )
|
|
return false;
|
|
|
|
if( is_utf8_continuation_byte( s[start] ) || /* misplaced continuation byte */
|
|
(s[start] & 0xFE) == 0xFE ) /* 0xFE, 0xFF */
|
|
{
|
|
start += 1;
|
|
return false;
|
|
}
|
|
|
|
int len = utf8_get_char_len( s[start] );
|
|
|
|
const int first_byte_mask[] = { -1, 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
|
|
|
|
ch = wchar_t(s[start] & first_byte_mask[len]);
|
|
|
|
for( int i = 1; i < len; ++i )
|
|
{
|
|
if( start+i >= s.size() )
|
|
{
|
|
/* We expected a continuation byte, but didn't get one. Return error, and point
|
|
* start at the unexpected byte; it's probably a new sequence. */
|
|
start += i;
|
|
return false;
|
|
}
|
|
|
|
char byte = s[start+i];
|
|
if( !is_utf8_continuation_byte(byte) )
|
|
{
|
|
/* We expected a continuation byte, but didn't get one. Return error, and point
|
|
* start at the unexpected byte; it's probably a new sequence. */
|
|
start += i;
|
|
return false;
|
|
}
|
|
ch = (ch << 6) | (byte & 0x3F);
|
|
}
|
|
|
|
bool bValid = true;
|
|
{
|
|
unsigned c1 = (unsigned) s[start] & 0xFF;
|
|
unsigned c2 = (unsigned) s[start+1] & 0xFF;
|
|
int c = (c1 << 8) + c2;
|
|
if( (c & 0xFE00) == 0xC000 ||
|
|
(c & 0xFFE0) == 0xE080 ||
|
|
(c & 0xFFF0) == 0xF080 ||
|
|
(c & 0xFFF8) == 0xF880 ||
|
|
(c & 0xFFFC) == 0xFC80 )
|
|
{
|
|
bValid = false;
|
|
}
|
|
}
|
|
|
|
if( ch == 0xFFFE || ch == 0xFFFF )
|
|
bValid = false;
|
|
|
|
start += len;
|
|
return bValid;
|
|
}
|
|
|
|
/* Like utf8_to_wchar_ec, but only does enough error checking to prevent crashing. */
|
|
bool utf8_to_wchar( const char *s, size_t iLength, unsigned &start, wchar_t &ch )
|
|
{
|
|
if( start >= iLength )
|
|
return false;
|
|
|
|
int len = utf8_get_char_len( s[start] );
|
|
|
|
if( start+len > iLength )
|
|
{
|
|
/* We don't have room for enough continuation bytes. Return error. */
|
|
start += len;
|
|
ch = L'?';
|
|
return false;
|
|
}
|
|
|
|
switch( len )
|
|
{
|
|
case 1:
|
|
ch = (s[start+0] & 0x7F);
|
|
break;
|
|
case 2:
|
|
ch = ( (s[start+0] & 0x1F) << 6 ) |
|
|
(s[start+1] & 0x3F);
|
|
break;
|
|
case 3:
|
|
ch = ( (s[start+0] & 0x0F) << 12 ) |
|
|
( (s[start+1] & 0x3F) << 6 ) |
|
|
(s[start+2] & 0x3F);
|
|
break;
|
|
case 4:
|
|
ch = ( (s[start+0] & 0x07) << 18 ) |
|
|
( (s[start+1] & 0x3F) << 12 ) |
|
|
( (s[start+2] & 0x3F) << 6 ) |
|
|
(s[start+3] & 0x3F);
|
|
break;
|
|
case 5:
|
|
ch = ( (s[start+0] & 0x03) << 24 ) |
|
|
( (s[start+1] & 0x3F) << 18 ) |
|
|
( (s[start+2] & 0x3F) << 12 ) |
|
|
( (s[start+3] & 0x3F) << 6 ) |
|
|
(s[start+4] & 0x3F);
|
|
break;
|
|
|
|
case 6:
|
|
ch = ( (s[start+0] & 0x01) << 30 ) |
|
|
( (s[start+1] & 0x3F) << 24 ) |
|
|
( (s[start+2] & 0x3F) << 18 ) |
|
|
( (s[start+3] & 0x3F) << 12) |
|
|
( (s[start+4] & 0x3F) << 6 ) |
|
|
(s[start+5] & 0x3F);
|
|
break;
|
|
|
|
}
|
|
|
|
start += len;
|
|
return true;
|
|
}
|
|
|
|
|
|
/* UTF-8 encode ch and append to out. */
|
|
void wchar_to_utf8( wchar_t ch, RString &out )
|
|
{
|
|
if( ch < 0x80 ) { out.append( 1, (char) ch ); return; }
|
|
|
|
int cbytes = 0;
|
|
if( ch < 0x800 ) cbytes = 1;
|
|
else if( ch < 0x10000 ) cbytes = 2;
|
|
else if( ch < 0x200000 ) cbytes = 3;
|
|
else if( ch < 0x4000000 ) cbytes = 4;
|
|
else cbytes = 5;
|
|
|
|
{
|
|
int shift = cbytes*6;
|
|
const int init_masks[] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
|
|
out.append( 1, (char) (init_masks[cbytes-1] | (ch>>shift)) );
|
|
}
|
|
|
|
for( int i = 0; i < cbytes; ++i )
|
|
{
|
|
int shift = (cbytes-i-1)*6;
|
|
out.append( 1, (char) (0x80 | ((ch>>shift)&0x3F)) );
|
|
}
|
|
}
|
|
|
|
|
|
wchar_t utf8_get_char( const RString &s )
|
|
{
|
|
unsigned start = 0;
|
|
wchar_t ret;
|
|
if( !utf8_to_wchar_ec( s, start, ret ) )
|
|
return INVALID_CHAR;
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/* Replace invalid sequences in s. */
|
|
void utf8_sanitize( RString &s )
|
|
{
|
|
RString ret;
|
|
for( unsigned start = 0; start < s.size(); )
|
|
{
|
|
wchar_t ch;
|
|
if( !utf8_to_wchar_ec( s, start, ch ) )
|
|
ch = INVALID_CHAR;
|
|
|
|
wchar_to_utf8( ch, ret );
|
|
}
|
|
|
|
s = ret;
|
|
}
|
|
|
|
|
|
bool utf8_is_valid( const RString &s )
|
|
{
|
|
for( unsigned start = 0; start < s.size(); )
|
|
{
|
|
wchar_t ch;
|
|
if( !utf8_to_wchar_ec( s, start, ch ) )
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Windows tends to drop garbage BOM characters at the start of UTF-8 text files.
|
|
* Remove them. */
|
|
void utf8_remove_bom( RString &sLine )
|
|
{
|
|
if( !sLine.compare(0, 3, "\xef\xbb\xbf") )
|
|
sLine.erase(0, 3);
|
|
}
|
|
|
|
static int UnicodeDoUpper( char *p, size_t iLen, const unsigned char pMapping[256] )
|
|
{
|
|
wchar_t wc = L'\0';
|
|
unsigned iStart = 0;
|
|
if( !utf8_to_wchar(p, iLen, iStart, wc) )
|
|
return 1;
|
|
|
|
wchar_t iUpper = wc;
|
|
if( wc < 256 )
|
|
iUpper = pMapping[wc];
|
|
if( iUpper != wc )
|
|
{
|
|
RString sOut;
|
|
wchar_to_utf8( iUpper, sOut );
|
|
if( sOut.size() == iStart )
|
|
memcpy( p, sOut.data(), sOut.size() );
|
|
else
|
|
WARN( ssprintf("UnicodeDoUpper: invalid character at \"%s\"", RString(p,iLen).c_str()) );
|
|
}
|
|
|
|
return iStart;
|
|
}
|
|
|
|
/* Fast in-place MakeUpper and MakeLower. This only replaces characters with characters of the same UTF-8
|
|
* length, so we never have to move the whole string. This is optimized for strings that have no
|
|
* non-ASCII characters. */
|
|
void MakeUpper( char *p, size_t iLen )
|
|
{
|
|
char *pStart = p;
|
|
char *pEnd = p + iLen;
|
|
while( p < pEnd )
|
|
{
|
|
/* Fast path: */
|
|
if( likely( !(*p & 0x80) ) )
|
|
{
|
|
if( unlikely(*p >= 'a' && *p <= 'z') )
|
|
*p += 'A' - 'a';
|
|
++p;
|
|
continue;
|
|
}
|
|
|
|
int iRemaining = iLen - (p-pStart);
|
|
p += UnicodeDoUpper( p, iRemaining, g_UpperCase );
|
|
}
|
|
}
|
|
|
|
void MakeLower( char *p, size_t iLen )
|
|
{
|
|
char *pStart = p;
|
|
char *pEnd = p + iLen;
|
|
while( p < pEnd )
|
|
{
|
|
/* Fast path: */
|
|
if( likely( !(*p & 0x80) ) )
|
|
{
|
|
if( unlikely(*p >= 'A' && *p <= 'Z') )
|
|
*p -= 'A' - 'a';
|
|
++p;
|
|
continue;
|
|
}
|
|
|
|
int iRemaining = iLen - (p-pStart);
|
|
p += UnicodeDoUpper( p, iRemaining, g_LowerCase );
|
|
}
|
|
}
|
|
|
|
void UnicodeUpperLower( wchar_t *p, size_t iLen, const unsigned char pMapping[256] )
|
|
{
|
|
wchar_t *pEnd = p + iLen;
|
|
while( p != pEnd )
|
|
{
|
|
if( *p < 256 )
|
|
*p = pMapping[*p];
|
|
++p;
|
|
}
|
|
}
|
|
|
|
void MakeUpper( wchar_t *p, size_t iLen )
|
|
{
|
|
UnicodeUpperLower( p, iLen, g_UpperCase );
|
|
}
|
|
|
|
void MakeLower( wchar_t *p, size_t iLen )
|
|
{
|
|
UnicodeUpperLower( p, iLen, g_LowerCase );
|
|
}
|
|
|
|
int StringToInt( const RString &sString )
|
|
{
|
|
int ret;
|
|
istringstream ( sString ) >> ret;
|
|
return ret;
|
|
}
|
|
|
|
RString IntToString( const int &iNum )
|
|
{
|
|
stringstream ss;
|
|
ss << iNum;
|
|
return ss.str();
|
|
}
|
|
|
|
float StringToFloat( const RString &sString )
|
|
{
|
|
float ret = strtof( sString, NULL );
|
|
|
|
if( !isfinite(ret) )
|
|
ret = 0.0f;
|
|
return ret;
|
|
}
|
|
|
|
bool StringToFloat( const RString &sString, float &fOut )
|
|
{
|
|
char *endPtr;
|
|
|
|
fOut = strtof( sString, &endPtr );
|
|
return sString.size() && *endPtr == '\0' && isfinite( fOut );
|
|
}
|
|
|
|
const wchar_t INVALID_CHAR = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */
|
|
|
|
wstring RStringToWstring( const RString &s )
|
|
{
|
|
wstring ret;
|
|
ret.reserve( s.size() );
|
|
for( unsigned start = 0; start < s.size(); )
|
|
{
|
|
char c = s[start];
|
|
if( !(c&0x80) )
|
|
{
|
|
/* ASCII fast path */
|
|
ret += c;
|
|
++start;
|
|
continue;
|
|
}
|
|
|
|
wchar_t ch = L'\0';
|
|
if( !utf8_to_wchar( s.data(), s.size(), start, ch ) )
|
|
ch = INVALID_CHAR;
|
|
ret += ch;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
RString WStringToRString( const wstring &sStr )
|
|
{
|
|
RString sRet;
|
|
|
|
for( unsigned i = 0; i < sStr.size(); ++i )
|
|
wchar_to_utf8( sStr[i], sRet );
|
|
|
|
return sRet;
|
|
}
|
|
|
|
|
|
RString WcharToUTF8( wchar_t c )
|
|
{
|
|
RString ret;
|
|
wchar_to_utf8( c, ret );
|
|
return ret;
|
|
}
|
|
|
|
/* &a; -> a */
|
|
void ReplaceEntityText( RString &sText, const map<RString,RString> &m )
|
|
{
|
|
RString sRet;
|
|
|
|
size_t iOffset = 0;
|
|
while( iOffset != sText.size() )
|
|
{
|
|
size_t iStart = sText.find( '&', iOffset );
|
|
if( iStart == sText.npos )
|
|
{
|
|
/* Optimization: if we didn't replace anything at all, do nothing. */
|
|
if( iOffset == 0 )
|
|
return;
|
|
|
|
// Append the rest of the string.
|
|
sRet.append( sText, iOffset, sRet.npos );
|
|
break;
|
|
}
|
|
|
|
// Append the text between iOffset and iStart.
|
|
sRet.append( sText, iOffset, iStart-iOffset );
|
|
iOffset += iStart-iOffset;
|
|
|
|
// Optimization: stop early on "&", so "&&&&&&&&&&&" isn't n^2.
|
|
size_t iEnd = sText.find_first_of( "&;", iStart+1 );
|
|
if( iEnd == sText.npos || sText[iEnd] == '&' )
|
|
{
|
|
/* & with no matching ;, or two & in a row. Append the & and
|
|
* continue. */
|
|
sRet.append( sText, iStart, 1 );
|
|
++iOffset;
|
|
continue;
|
|
}
|
|
|
|
RString sElement = sText.substr( iStart+1, iEnd-iStart-1 );
|
|
sElement.MakeLower();
|
|
|
|
map<RString,RString>::const_iterator it = m.find( sElement );
|
|
if( it == m.end() )
|
|
{
|
|
sRet.append( sText, iStart, iEnd-iStart+1 );
|
|
iOffset = iEnd + 1;
|
|
continue;
|
|
}
|
|
|
|
const RString &sTo = it->second;
|
|
sRet.append( sTo );
|
|
iOffset = iEnd + 1;
|
|
}
|
|
|
|
sText = sRet;
|
|
}
|
|
|
|
/* abcd -> &a; &b; &c; &d; */
|
|
void ReplaceEntityText( RString &sText, const map<char,RString> &m )
|
|
{
|
|
RString sFind;
|
|
|
|
FOREACHM_CONST( char, RString, m, c )
|
|
sFind.append( 1, c->first );
|
|
|
|
RString sRet;
|
|
|
|
size_t iOffset = 0;
|
|
while( iOffset != sText.size() )
|
|
{
|
|
size_t iStart = sText.find_first_of( sFind, iOffset );
|
|
if( iStart == sText.npos )
|
|
{
|
|
/* Optimization: if we didn't replace anything at all, do nothing. */
|
|
if( iOffset == 0 )
|
|
return;
|
|
|
|
// Append the rest of the string.
|
|
sRet.append( sText, iOffset, sRet.npos );
|
|
break;
|
|
}
|
|
|
|
// Append the text between iOffset and iStart.
|
|
sRet.append( sText, iOffset, iStart-iOffset );
|
|
iOffset += iStart-iOffset;
|
|
|
|
char sElement = sText[iStart];
|
|
|
|
map<char,RString>::const_iterator it = m.find( sElement );
|
|
ASSERT( it != m.end() );
|
|
|
|
const RString &sTo = it->second;
|
|
sRet.append( 1, '&' );
|
|
sRet.append( sTo );
|
|
sRet.append( 1, ';' );
|
|
++iOffset;
|
|
}
|
|
|
|
sText = sRet;
|
|
}
|
|
|
|
/* Replace &#nnnn; (decimal) and &xnnnn; (hex) with corresponding UTF-8 characters. */
|
|
void Replace_Unicode_Markers( RString &sText )
|
|
{
|
|
unsigned iStart = 0;
|
|
while( iStart < sText.size() )
|
|
{
|
|
/* Look for &#digits; */
|
|
bool bHex = false;
|
|
size_t iPos = sText.find( "&#", iStart );
|
|
if( iPos == sText.npos )
|
|
{
|
|
bHex = true;
|
|
iPos = sText.find( "&x", iStart );
|
|
}
|
|
|
|
if( iPos == sText.npos )
|
|
break;
|
|
iStart = iPos+1;
|
|
|
|
unsigned p = iPos;
|
|
p += 2;
|
|
|
|
/* Found &# or &x. Is it followed by digits and a semicolon? */
|
|
if( p >= sText.size() )
|
|
continue;
|
|
|
|
int iNumDigits = 0;
|
|
while( p < sText.size() && bHex? isxdigit(sText[p]):isdigit(sText[p]) )
|
|
{
|
|
p++;
|
|
iNumDigits++;
|
|
}
|
|
|
|
if( !iNumDigits )
|
|
continue; /* must have at least one digit */
|
|
if( p >= sText.size() || sText[p] != ';' )
|
|
continue;
|
|
p++;
|
|
|
|
int iNum;
|
|
if( bHex )
|
|
sscanf( sText.c_str()+iPos, "&x%x;", &iNum );
|
|
else
|
|
sscanf( sText.c_str()+iPos, "&#%i;", &iNum );
|
|
if( iNum > 0xFFFF )
|
|
iNum = INVALID_CHAR;
|
|
|
|
sText.replace( iPos, p-iPos, WcharToUTF8(wchar_t(iNum)) );
|
|
}
|
|
}
|
|
|
|
/* Form a string to identify a wchar_t with ASCII. */
|
|
RString WcharDisplayText( wchar_t c )
|
|
{
|
|
RString sChr;
|
|
sChr = ssprintf( "U+%4.4x", c );
|
|
if( c < 128 )
|
|
sChr += ssprintf( " ('%c')", char(c) );
|
|
return sChr;
|
|
}
|
|
|
|
/* Return the last named component of dir:
|
|
* a/b/c -> c
|
|
* a/b/c/ -> c
|
|
*/
|
|
RString Basename( const RString &sDir )
|
|
{
|
|
size_t iEnd = sDir.find_last_not_of( "/\\" );
|
|
if( iEnd == sDir.npos )
|
|
return RString();
|
|
|
|
size_t iStart = sDir.find_last_of( "/\\", iEnd );
|
|
if( iStart == sDir.npos )
|
|
iStart = 0;
|
|
else
|
|
++iStart;
|
|
|
|
return sDir.substr( iStart, iEnd-iStart+1 );
|
|
}
|
|
|
|
/*
|
|
* Return all but the last named component of dir:
|
|
*
|
|
* a/b/c -> a/b/
|
|
* a/b/c/ -> a/b/
|
|
* c/ -> ./
|
|
* /foo -> /
|
|
* / -> /
|
|
*/
|
|
RString Dirname( const RString &dir )
|
|
{
|
|
/* Special case: "/" -> "/". */
|
|
if( dir.size() == 1 && dir[0] == '/' )
|
|
return "/";
|
|
|
|
int pos = dir.size()-1;
|
|
/* Skip trailing slashes. */
|
|
while( pos >= 0 && dir[pos] == '/' )
|
|
--pos;
|
|
|
|
/* Skip the last component. */
|
|
while( pos >= 0 && dir[pos] != '/' )
|
|
--pos;
|
|
|
|
if( pos < 0 )
|
|
return "./";
|
|
|
|
return dir.substr(0, pos+1);
|
|
}
|
|
|
|
RString Capitalize( const RString &s )
|
|
{
|
|
if( s.empty() )
|
|
return RString();
|
|
|
|
RString s2 = s;
|
|
char *pBuf = s2.GetBuffer();
|
|
UnicodeDoUpper( pBuf, s2.size(), g_UpperCase );
|
|
s2.ReleaseBuffer();
|
|
|
|
return s2;
|
|
}
|
|
|
|
unsigned char g_UpperCase[256] =
|
|
{
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
|
|
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
|
|
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
|
|
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
|
|
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
|
|
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
|
|
0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
|
|
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x7F,
|
|
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
|
|
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
|
|
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
|
|
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
|
|
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
|
|
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
|
|
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
|
|
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF,
|
|
};
|
|
|
|
unsigned char g_LowerCase[256] =
|
|
{
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
|
|
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
|
|
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
|
|
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
|
|
0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
|
|
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F,
|
|
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
|
|
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
|
|
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
|
|
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
|
|
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
|
|
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
|
|
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
|
|
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
|
|
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
|
|
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF,
|
|
};
|
|
|
|
void FixSlashesInPlace( RString &sPath )
|
|
{
|
|
for( unsigned i = 0; i < sPath.size(); ++i )
|
|
if( sPath[i] == '\\' )
|
|
sPath[i] = '/';
|
|
}
|
|
|
|
/*
|
|
* Keep trailing slashes, since that can be used to illustrate that a path always
|
|
* represents a directory.
|
|
*
|
|
* foo/bar -> foo/bar
|
|
* foo/bar/ -> foo/bar/
|
|
* foo///bar/// -> foo/bar/
|
|
* foo/bar/./baz -> foo/bar/baz
|
|
* foo/bar/../baz -> foo/baz
|
|
* ../foo -> ../foo
|
|
* ../../foo -> ../../foo
|
|
* ./foo -> foo (if bRemoveLeadingDot), ./foo (if !bRemoveLeadingDot)
|
|
* ./ -> ./
|
|
* ./// -> ./
|
|
*/
|
|
|
|
void CollapsePath( RString &sPath, bool bRemoveLeadingDot )
|
|
{
|
|
RString sOut;
|
|
sOut.reserve( sPath.size() );
|
|
|
|
size_t iPos = 0;
|
|
size_t iNext;
|
|
for( ; iPos < sPath.size(); iPos = iNext )
|
|
{
|
|
/* Find the next slash. */
|
|
iNext = sPath.find( '/', iPos );
|
|
if( iNext == RString::npos )
|
|
iNext = sPath.size();
|
|
else
|
|
++iNext;
|
|
|
|
/* Strip extra slashes, but don't remove slashes from the beginning of the string. */
|
|
if( iNext - iPos == 1 && sPath[iPos] == '/' )
|
|
{
|
|
if( !sOut.empty() )
|
|
continue;
|
|
}
|
|
|
|
/* If this is a dot, skip it. */
|
|
if( iNext - iPos == 2 && sPath[iPos] == '.' && sPath[iPos+1] == '/' )
|
|
{
|
|
if( bRemoveLeadingDot || !sOut.empty() )
|
|
continue;
|
|
}
|
|
|
|
/* If this is two dots, */
|
|
if( iNext - iPos == 3 && sPath[iPos] == '.' && sPath[iPos+1] == '.' && sPath[iPos+2] == '/' )
|
|
{
|
|
/* If this is the first path element (nothing to delete), or all we have is a slash,
|
|
* leave it. */
|
|
if( sOut.empty() || (sOut.size() == 1 && sOut[0] == '/') )
|
|
{
|
|
sOut.append( sPath, iPos, iNext-iPos );
|
|
continue;
|
|
}
|
|
|
|
/* Search backwards for the previous path element. */
|
|
size_t iPrev = sOut.rfind( '/', sOut.size()-2 );
|
|
if( iPrev == RString::npos )
|
|
iPrev = 0;
|
|
else
|
|
++iPrev;
|
|
|
|
/* If the previous element is also .., leave it. */
|
|
bool bLastIsTwoDots = (sOut.size() - iPrev == 3 && sOut[iPrev] == '.' && sOut[iPrev+1] == '.' );
|
|
if( bLastIsTwoDots )
|
|
{
|
|
sOut.append( sPath, iPos, iNext-iPos );
|
|
continue;
|
|
}
|
|
|
|
sOut.erase( iPrev );
|
|
continue;
|
|
}
|
|
|
|
sOut.append( sPath, iPos, iNext-iPos );
|
|
}
|
|
|
|
sOut.swap( sPath );
|
|
}
|
|
|
|
namespace StringConversion
|
|
{
|
|
template<> bool FromString<int>( const RString &sValue, int &out )
|
|
{
|
|
if( sscanf( sValue.c_str(), "%d", &out ) == 1 )
|
|
return true;
|
|
|
|
out = 0;
|
|
return false;
|
|
}
|
|
|
|
template<> bool FromString<unsigned>( const RString &sValue, unsigned &out )
|
|
{
|
|
if( sscanf( sValue.c_str(), "%u", &out ) == 1 )
|
|
return true;
|
|
|
|
out = 0;
|
|
return false;
|
|
}
|
|
|
|
template<> bool FromString<float>( const RString &sValue, float &out )
|
|
{
|
|
const char *endptr = sValue.data() + sValue.size();
|
|
out = strtof( sValue, (char **) &endptr );
|
|
if( endptr != sValue.data() && isfinite( out ) )
|
|
return true;
|
|
out = 0;
|
|
return false;
|
|
}
|
|
|
|
template<> bool FromString<bool>( const RString &sValue, bool &out )
|
|
{
|
|
if( sValue.size() == 0 )
|
|
return false;
|
|
|
|
out = (StringToInt(sValue) != 0);
|
|
return true;
|
|
}
|
|
|
|
template<> RString ToString<int>( const int &value )
|
|
{
|
|
return ssprintf( "%i", value );
|
|
}
|
|
|
|
template<> RString ToString<unsigned>( const unsigned &value )
|
|
{
|
|
return ssprintf( "%u", value );
|
|
}
|
|
|
|
template<> RString ToString<float>( const float &value )
|
|
{
|
|
return ssprintf( "%f", value );
|
|
}
|
|
|
|
template<> RString ToString<bool>( const bool &value )
|
|
{
|
|
return ssprintf( "%i", value );
|
|
}
|
|
}
|
|
|
|
bool FileCopy( const RString &sSrcFile, const RString &sDstFile )
|
|
{
|
|
if( !sSrcFile.CompareNoCase(sDstFile) )
|
|
{
|
|
LOG->Warn( "Tried to copy \"%s\" over itself", sSrcFile.c_str() );
|
|
return false;
|
|
}
|
|
|
|
RageFile in;
|
|
if( !in.Open(sSrcFile, RageFile::READ) )
|
|
return false;
|
|
|
|
RageFile out;
|
|
if( !out.Open(sDstFile, RageFile::WRITE) )
|
|
return false;
|
|
|
|
RString sError;
|
|
if( !FileCopy(in, out, sError) )
|
|
{
|
|
LOG->Warn( "FileCopy(%s,%s): %s",
|
|
sSrcFile.c_str(), sDstFile.c_str(), sError.c_str() );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileCopy( RageFileBasic &in, RageFileBasic &out, RString &sError, bool *bReadError )
|
|
{
|
|
while(1)
|
|
{
|
|
RString data;
|
|
if( in.Read(data, 1024*32) == -1 )
|
|
{
|
|
sError = ssprintf( "read error: %s", in.GetError().c_str() );
|
|
if( bReadError != NULL )
|
|
*bReadError = true;
|
|
return false;
|
|
}
|
|
if( data.empty() )
|
|
break;
|
|
int i = out.Write(data);
|
|
if( i == -1 )
|
|
{
|
|
sError = ssprintf( "write error: %s", out.GetError().c_str() );
|
|
if( bReadError != NULL )
|
|
*bReadError = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( out.Flush() == -1 )
|
|
{
|
|
sError = ssprintf( "write error: %s", out.GetError().c_str() );
|
|
if( bReadError != NULL )
|
|
*bReadError = false;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
LuaFunction( SecondsToMSSMsMs, SecondsToMSSMsMs( FArg(1) ) )
|
|
LuaFunction( SecondsToHHMMSS, SecondsToHHMMSS( FArg(1) ) )
|
|
LuaFunction( SecondsToMMSSMsMs, SecondsToMMSSMsMs( FArg(1) ) )
|
|
LuaFunction( SecondsToMMSSMsMsMs, SecondsToMMSSMsMsMs( FArg(1) ) )
|
|
LuaFunction( SecondsToMSS, SecondsToMSS( FArg(1) ) )
|
|
LuaFunction( SecondsToMMSS, SecondsToMMSS( FArg(1) ) )
|
|
LuaFunction( FormatNumberAndSuffix, FormatNumberAndSuffix( IArg(1) ) )
|
|
LuaFunction( Basename, Basename( SArg(1) ) )
|
|
static RString MakeLower( RString s ) { s.MakeLower(); return s; }
|
|
LuaFunction( Lowercase, MakeLower( SArg(1) ) )
|
|
static RString MakeUpper( RString s ) { s.MakeUpper(); return s; }
|
|
LuaFunction( Uppercase, MakeUpper( SArg(1) ) )
|
|
LuaFunction( mbstrlen, (int)RStringToWstring(SArg(1)).length() )
|
|
LuaFunction( URLEncode, URLEncode( SArg(1) ) );
|
|
//LuaFunction( IsHexVal, IsHexVal( SArg(1) ) );
|
|
|
|
/*
|
|
* Copyright (c) 2001-2005 Chris Danford, Glenn Maynard
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
|
* whom the Software is furnished to do so, provided that the above
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
|
* the Software and that both the above copyright notice(s) and this
|
|
* permission notice appear in supporting documentation.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|