1311 lines
26 KiB
C++
1311 lines
26 KiB
C++
/* -----------------------------------------------------------------------------
|
|
GSFramework
|
|
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
|
|
----------------------------------------------------------------------------- */
|
|
|
|
|
|
#include <cstdio>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
|
|
#if __PLATFORM_WINDOWS__
|
|
#include <cstdarg>
|
|
#elif __PLATFORM_NINTENDO_WII__
|
|
#include <revolution/os.h>
|
|
#elif __PLATFORM_POSIX__
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
#include "nstring/nstring.h"
|
|
#include "container/nlist.h"
|
|
#include "container/narray.h"
|
|
#include "platform_config.h"
|
|
#include "alloc/ialloc.h"
|
|
|
|
using namespace GS;
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
static char __lowercase(char c)
|
|
{
|
|
if ((c >= 'A') && (c <= 'Z'))
|
|
c = c - 'A' + 'a';
|
|
return c;
|
|
}
|
|
static bool __isValidSymbolCharacter(char c)
|
|
{
|
|
return (
|
|
((c >= 'a') && (c <= 'z')) ||
|
|
((c >= 'A') && (c <= 'Z')) ||
|
|
((c >= '0') && (c <= '9')) ||
|
|
(c == '_')
|
|
);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
#if (__PLATFORM_LINUX__ || __PLATFORM_OSX__ || __PLATFORM_IOS__ || __PLATFORM_ANDROID_NDK__)
|
|
void strupr(char *s)
|
|
{
|
|
for (; s[0]; ++s)
|
|
if ((s[0] >= 'a') || (s[0] <= 'z'))
|
|
s[0] = toupper(s[0]);
|
|
}
|
|
void strlwr(char *s)
|
|
{
|
|
for (; s[0]; ++s)
|
|
if ((s[0] >= 0x41) || (s[0] <= 0x5A))
|
|
s[0] = tolower(s[0]);
|
|
}
|
|
#endif
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
void String::NormalizeEOL(EOLConvention to_eol)
|
|
{
|
|
switch (to_eol)
|
|
{
|
|
case EOLUnix:
|
|
{
|
|
uint invalid_eol_count = 0;
|
|
for (char *p = _p.c_ptr(); p[0] && p[1]; ++p)
|
|
if ((p[0] == '\r') && (p[1] == '\n'))
|
|
{
|
|
++invalid_eol_count;
|
|
++p;
|
|
}
|
|
|
|
Array <char> _o(Size() - invalid_eol_count + 1); // remove 1 byte per invalid EOL
|
|
|
|
char *o = _o.c_ptr(), *p = _p.c_ptr();
|
|
for ( ; p[0] && p[1]; ++p)
|
|
if ((p[0] == '\r') && (p[1] == '\n'))
|
|
{
|
|
*o++ = '\n';
|
|
++p;
|
|
}
|
|
else
|
|
*o++ = p[0];
|
|
|
|
if (p[0] != 0)
|
|
*o++ = *p++;
|
|
|
|
*o = 0;
|
|
_p.Transfer(_o);
|
|
}
|
|
break;
|
|
|
|
case EOLWindows:
|
|
{
|
|
uint invalid_eol_count = 0;
|
|
char *p = _p.c_ptr();
|
|
for (int n = 0; p[n]; ++n)
|
|
if ((p[n] == '\n') && ((n > 0) && (p[n - 1] != '\r')))
|
|
++invalid_eol_count;
|
|
|
|
Array <char> _o(Size() + invalid_eol_count + 1); // add 1 byte per invalid EOL
|
|
|
|
char *o = _o.c_ptr();
|
|
p = _p.c_ptr();
|
|
for (int n = 0; p[n]; ++n)
|
|
if ((p[n] == '\n') && ((n > 0) && (p[n - 1] != '\r')))
|
|
{
|
|
*o++ = '\r';
|
|
*o++ = '\n';
|
|
}
|
|
else
|
|
*o++ = p[n];
|
|
|
|
*o = 0;
|
|
_p.Transfer(_o);
|
|
}
|
|
break;
|
|
}
|
|
Touch();
|
|
}
|
|
String String::NormalizedEOL(EOLConvention to_eol) const
|
|
{
|
|
String o(*this);
|
|
o.NormalizeEOL(to_eol);
|
|
return o;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
uint String::Len() const
|
|
{
|
|
if (len == -1)
|
|
(const_cast <int &> (len)) = _p.IsValid() ? strlen(_p) : 0;
|
|
return len;
|
|
}
|
|
size_t String::Size() const
|
|
{
|
|
size_t size = 0;
|
|
for (char *p = _p; p[0]; ++p)
|
|
++size;
|
|
return size;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String String::RewritePatternAll(const char *p, const char *pattern, RewriteFunc &rewrite_func)
|
|
{
|
|
String out;
|
|
|
|
forever
|
|
{
|
|
const char *s, *e;
|
|
s = String::LocatePattern(p, pattern, &e);
|
|
|
|
if (!s)
|
|
{
|
|
// append reminder
|
|
out += String(p, p + String::strlen(p));
|
|
break;
|
|
}
|
|
|
|
// append skipped segment
|
|
out += String(p, s);
|
|
|
|
// extract args...
|
|
List <String> args;
|
|
|
|
String source(s, e);
|
|
if (!source.Extract(pattern, args))
|
|
break;
|
|
|
|
// ...do rewrite.
|
|
out += rewrite_func(source, args);
|
|
|
|
p = e;
|
|
}
|
|
return out;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::Extract(const char *format, List <String> &arg)
|
|
{
|
|
arg.Clear();
|
|
|
|
const char *o = format;
|
|
for (const char *i = c_str(); i[0] && o[0]; )
|
|
{
|
|
if (o[0] == '%')
|
|
{
|
|
++o;
|
|
|
|
// get arg value
|
|
const char *e = i;
|
|
for (; (e[0] != o[0]) && e[0]; ++e);
|
|
|
|
arg.Add(String(i, e));
|
|
i = e;
|
|
}
|
|
else
|
|
{
|
|
if (i[0] != o[0])
|
|
{
|
|
// ignore character mismatch on space
|
|
if (i[0] == ' ')
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
// source format mismatch
|
|
return false;
|
|
}
|
|
|
|
++i;
|
|
++o;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
const char *String::LocatePattern(const char *s, const char *format, const char **e)
|
|
{
|
|
for (; s[0]; ++s)
|
|
{
|
|
const char *o = format;
|
|
|
|
if (s[0] != o[0])
|
|
continue;
|
|
|
|
const char *i = s;
|
|
while (i[0] && o[0])
|
|
{
|
|
if (o[0] == '%')
|
|
{
|
|
++o;
|
|
for (; (i[0] != o[0]) && i[0]; ++i);
|
|
}
|
|
else
|
|
{
|
|
if (i[0] != o[0])
|
|
{
|
|
// ignore character mismatch on space
|
|
if (i[0] == ' ')
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
// source format mismatch
|
|
o = NULL;
|
|
break;
|
|
}
|
|
|
|
++i;
|
|
++o;
|
|
}
|
|
}
|
|
if (o == NULL)
|
|
continue;
|
|
|
|
if (e)
|
|
*e = i;
|
|
return s;
|
|
}
|
|
return NULL;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::Compare(const char *s, CaseSensitivity cs) const
|
|
{
|
|
char *p = _p.c_ptr();
|
|
|
|
if (!s)
|
|
return true;
|
|
if (!p)
|
|
return false;
|
|
|
|
if (cs == CaseSensitive)
|
|
{
|
|
while (*p && *s)
|
|
if (*p++ != *s++)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
while (*p && *s)
|
|
if (__lowercase(*p++) != __lowercase(*s++))
|
|
return false;
|
|
}
|
|
return p[0] == s[0];
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::StartsWith(const char *b, CaseSensitivity cs) const
|
|
{
|
|
uint b_len = strlen(b);
|
|
if (b_len > Len())
|
|
return false;
|
|
return c_str() ? Left(b_len).Compare(b, cs) : false;
|
|
}
|
|
bool String::EndsWith(const char *b, CaseSensitivity cs) const
|
|
{
|
|
uint b_len = strlen(b);
|
|
if (b_len > Len())
|
|
return false;
|
|
return c_str() ? Right(b_len).Compare(b, cs) : false;
|
|
}
|
|
bool String::Contains(const char *b, CaseSensitivity cs) const
|
|
{ return c_str() ? asbool(FindString(b, cs)) : false; }
|
|
bool String::Replace(const char *what, const char *by, CaseSensitivity cs)
|
|
{
|
|
char *sp = _p.c_ptr() ? FindString(what, 0, cs) : NULL;
|
|
if (!sp)
|
|
return false;
|
|
|
|
uint what_len = strlen(what),
|
|
by_len = strlen(by);
|
|
uint final_len = Len() - what_len + by_len;
|
|
|
|
Array <char> np(final_len + 1, Alloc::StringBuffer);
|
|
char *wp = np;
|
|
if (np.IsNull())
|
|
return false;
|
|
|
|
memcpy(wp, _p.c_ptr(), sp - _p.c_ptr());
|
|
wp += sp - _p;
|
|
memcpy(wp, by, by_len);
|
|
wp += by_len;
|
|
memcpy(wp, sp + what_len, (_p + Len()) - (sp + what_len) + 1);
|
|
|
|
Clear();
|
|
_p = np;
|
|
return true;
|
|
}
|
|
bool String::ReplaceAll(const char **what, const char **by, bool match_whole_word)
|
|
{
|
|
if (!_p)
|
|
return false; // no replace
|
|
|
|
uint final_len = Len();
|
|
|
|
Array <char> p_out(Alloc::StringBuffer);
|
|
char *_p_out = NULL;
|
|
|
|
for (uint p = 0; p < 2; ++p)
|
|
{
|
|
bool has_replacement = false;
|
|
for (char *c_p = _p; *c_p; )
|
|
{
|
|
// Search for a match.
|
|
uint n;
|
|
for (n = 0; what[n] && by[n]; ++n)
|
|
if (!strccmp(c_p, what[n]))
|
|
break;
|
|
|
|
if (what[n] && by[n])
|
|
{
|
|
// Got a match.
|
|
size_t what_len = strlen(what[n]),
|
|
by_len = strlen(by[n]);
|
|
|
|
// Check whole word constraint.
|
|
bool do_replace = true;
|
|
|
|
if (match_whole_word)
|
|
{
|
|
if ((c_p > _p) && __isValidSymbolCharacter(c_p[-1]))
|
|
do_replace = false;
|
|
else
|
|
if (c_p[what_len] && __isValidSymbolCharacter(c_p[what_len]))
|
|
do_replace = false;
|
|
}
|
|
|
|
if (do_replace)
|
|
{
|
|
if (_p_out)
|
|
{
|
|
strncpy(_p_out, by[n], by_len);
|
|
_p_out += by_len;
|
|
}
|
|
else
|
|
final_len += by_len - what_len;
|
|
|
|
has_replacement = true;
|
|
}
|
|
else
|
|
if (_p_out)
|
|
{
|
|
strncpy(_p_out, c_p, what_len);
|
|
_p_out += what_len;
|
|
}
|
|
|
|
c_p += what_len;
|
|
}
|
|
else
|
|
{
|
|
if (_p_out)
|
|
*_p_out++ = *c_p;
|
|
++c_p;
|
|
}
|
|
}
|
|
|
|
if (!has_replacement)
|
|
return false; // no replace
|
|
|
|
if (!_p_out)
|
|
{
|
|
p_out.Allocate(final_len + 1);
|
|
_p_out = p_out;
|
|
}
|
|
}
|
|
|
|
// Commit modified buffer to string.
|
|
Clear();
|
|
p_out[final_len] = 0;
|
|
_p = p_out;
|
|
len = final_len;
|
|
hash = 0;
|
|
return true;
|
|
}
|
|
bool String::ReplaceAll(const char *what, const char *by, bool match_whole_word)
|
|
{
|
|
const char *_what[] = { what, NULL }, *_by[] = { by, NULL };
|
|
return ReplaceAll(_what, _by, match_whole_word);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
size_t String::Utf8toUtf32(const uchar *utf8, uint *utf32)
|
|
{
|
|
__ASSERT__(utf8);
|
|
__ASSERT__(utf32);
|
|
|
|
const uchar *p = utf8;
|
|
int length = GetUtf8CharSize(utf8);
|
|
|
|
switch (length)
|
|
{
|
|
case 4: *utf32 = (*p ^ 0xf0); break;
|
|
case 3: *utf32 = (*p ^ 0xe0); break;
|
|
case 2: *utf32 = (*p ^ 0xc0); break;
|
|
case 1: *utf32 = *p; break;
|
|
|
|
default:
|
|
*utf32 = 0;
|
|
break;
|
|
}
|
|
|
|
for (int n = length; n > 1; --n)
|
|
{
|
|
++p;
|
|
*utf32 <<= 6;
|
|
*utf32 |= (*p ^ 0x80);
|
|
}
|
|
return length;
|
|
}
|
|
size_t String::GetUtf8CharSize(const uchar *utf8)
|
|
{
|
|
__ASSERT__(utf8);
|
|
|
|
uchar c = utf8[0] >> 3;
|
|
|
|
// 6 => 0x7e
|
|
// 5 => 0x3e
|
|
if (c == 0x1e)
|
|
return 4;
|
|
|
|
c >>= 1;
|
|
if (c == 0xe)
|
|
return 3;
|
|
c >>= 1;
|
|
if (c == 0x6)
|
|
return 2;
|
|
|
|
return 1;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String String::FromUcs2(const ushort *ucs2)
|
|
{
|
|
Array <char> out(256);
|
|
size_t out_count = 0;
|
|
|
|
for (uint n = 0; ucs2[n]; ++n)
|
|
{
|
|
wchar_t w = ucs2[n];
|
|
|
|
if (out_count > (out.GetCount() - 4))
|
|
out.Reallocate(out.GetCount() * 2);
|
|
|
|
if (w <= 0x7f)
|
|
out[(int)out_count++] = (char)w;
|
|
|
|
else if (w <= 0x7ff)
|
|
{
|
|
out[(int)out_count++] = 0xc0 | ((w >> 6) & 0x1f);
|
|
out[(int)out_count++] = 0x80 | (w & 0x3f);
|
|
}
|
|
else /* if (w <= 0xffff) */
|
|
{
|
|
out[(int)out_count++] = 0xe0 | ((w >> 12) & 0x0f);
|
|
out[(int)out_count++] = 0x80 | ((w >> 6) & 0x3f);
|
|
out[(int)out_count++] = 0x80 | (w & 0x3f);
|
|
}
|
|
/* else if (w <= 0x10ffff)
|
|
{
|
|
out[out_count++] = 0xf0 | ((w >> 18) & 0x07);
|
|
out[out_count++] = 0x80 | ((w >> 12) & 0x3f);
|
|
out[out_count++] = 0x80 | ((w >> 6) & 0x3f);
|
|
out[out_count++] = 0x80 | (w & 0x3f);
|
|
}
|
|
else
|
|
out[out_count++] = '?'; */
|
|
}
|
|
|
|
return String(out.c_ptr(), out.c_ptr() + out_count);
|
|
}
|
|
Array <ushort> String::toUcs2() const
|
|
{
|
|
if (!_p)
|
|
return Array <ushort> (Alloc::StringBuffer);
|
|
|
|
// Get utf-8 character count.
|
|
int ccount = 0;
|
|
|
|
char *p = _p;
|
|
for (; p[0]; ++ccount)
|
|
p += GetUtf8CharSize((const uchar *)p);
|
|
|
|
// Allocate array.
|
|
Array <ushort> ucs2(ccount + 1, Alloc::StringBuffer);
|
|
|
|
p = _p;
|
|
for (ccount = 0; p[0]; ++ccount)
|
|
{
|
|
uint utf32;
|
|
p += Utf8toUtf32((const uchar *)p, &utf32);
|
|
ucs2[ccount] = utf32 & 0xffff;
|
|
}
|
|
ucs2[ccount] = 0;
|
|
|
|
return ucs2;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::IsAbsolutePath() const
|
|
{ return asbool((Len() > 2) && (c_str()[1] == ':')); }
|
|
String String::BeautifyFileName(bool allow_spaces) const
|
|
{
|
|
if (!_p)
|
|
return String();
|
|
|
|
Array <char> p(Len() + 1, Alloc::StringBuffer);
|
|
strncpy(p.c_ptr(), c_str(), Len() + 1);
|
|
|
|
for (uint n = 0; n < Len(); ++n)
|
|
{
|
|
if (p[n] == ' ')
|
|
{
|
|
if (!allow_spaces)
|
|
p[n] = '_';
|
|
}
|
|
else
|
|
{
|
|
if (p[n] == '_')
|
|
p[n] = ' ';
|
|
|
|
else
|
|
{
|
|
if (n > 0)
|
|
{
|
|
if ((p[n - 1] == ' ') && (p[n] >= 'a') && (p[n] <= 'z'))
|
|
p[n] += 'A' - 'a';
|
|
}
|
|
else
|
|
if ((p[n] >= 'a') && (p[n] <= 'z'))
|
|
p[n] += 'A' - 'a';
|
|
}
|
|
}
|
|
}
|
|
return String(&p[0]);
|
|
}
|
|
String String::CutFilePath() const
|
|
{
|
|
String b = *this;
|
|
b.FileCutPath();
|
|
return b;
|
|
}
|
|
String String::CutFileExtension() const
|
|
{
|
|
String b = *this;
|
|
b.FileCutExtension();
|
|
return b;
|
|
}
|
|
String String::CutFileName() const
|
|
{
|
|
String b = *this;
|
|
b.FileCutName();
|
|
return b;
|
|
}
|
|
String String::CleanFilePath() const
|
|
{
|
|
String b = *this;
|
|
b.FileCleanName();
|
|
return b;
|
|
}
|
|
String String::GetFileName() const
|
|
{
|
|
String b = *this;
|
|
b.FileCutPathAndExtension();
|
|
return b;
|
|
}
|
|
String String::GetFileNameAndExtension() const
|
|
{
|
|
String b = *this;
|
|
b.FileCutPath();
|
|
return b;
|
|
}
|
|
String String::GetFilePath() const
|
|
{
|
|
String b = *this;
|
|
b.FileCutName();
|
|
return b;
|
|
}
|
|
String String::GetFileExtension() const
|
|
{ return FileGetExtension(c_str()); }
|
|
String String::GetSwappedExtension(const char *ext) const
|
|
{
|
|
String b = *this;
|
|
b.FileSwapExtension(ext);
|
|
return b;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
int String::Compare(const String &c, const String &n)
|
|
{
|
|
const char *a = c.c_str(), *b = n.c_str();
|
|
|
|
if (!a || !b)
|
|
return a == b ? 0 : 1;
|
|
|
|
while (a[0] || b[0])
|
|
{
|
|
if (!b[0]) return -1;
|
|
if (!a[0]) return 1;
|
|
|
|
if (b[0] < a[0]) return -1;
|
|
if (b[0] > a[0]) return 1;
|
|
|
|
a++;
|
|
b++;
|
|
}
|
|
return 0;
|
|
}
|
|
String String::Upper() const
|
|
{
|
|
String s(_p);
|
|
if (!s.IsEmpty())
|
|
strupr(s._p);
|
|
s.Touch();
|
|
return s;
|
|
}
|
|
String String::Lower() const
|
|
{
|
|
String s(_p);
|
|
if (!s.IsEmpty())
|
|
strlwr(s._p);
|
|
s.Touch();
|
|
return s;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String String::Slice(uint s, uint e) const
|
|
{
|
|
if (s > Len()) s = Len();
|
|
if (e > Len()) e = Len();
|
|
return String(_p + s, _p + e);
|
|
}
|
|
String String::TrimChar(char c)
|
|
{
|
|
int occurence = 0;
|
|
for (char *p = _p; p[0]; p++)
|
|
if (p[0] == c)
|
|
occurence++;
|
|
if (!occurence)
|
|
return *this;
|
|
|
|
// Trim character occurrences.
|
|
Array <char> new_string(Len() - occurence + 1, Alloc::StringBuffer);
|
|
char *pnew_string = &new_string[0];
|
|
|
|
for (char *p = _p; p[0]; p++)
|
|
if (p[0] != c)
|
|
*pnew_string++ = p[0];
|
|
pnew_string[0] = 0;
|
|
|
|
return String(&new_string[0]);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
size_t String::IndexOf(char c, uint p) const
|
|
{
|
|
for (size_t n = p; n < Len(); ++n)
|
|
if (_p[(int)n] == c)
|
|
return n;
|
|
return (size_t)-1;
|
|
}
|
|
char *String::FindString(const char *c, uint p, CaseSensitivity cs) const
|
|
{
|
|
size_t len_c = strlen(c);
|
|
|
|
if (len_c <= Len())
|
|
for (size_t n = p; n <= (Len() - len_c); ++n)
|
|
{
|
|
size_t i = 0;
|
|
|
|
switch (cs)
|
|
{
|
|
case CaseSensitive:
|
|
for (; i < len_c; ++i)
|
|
if (_p[(int)(n + i)] != c[i])
|
|
break;
|
|
break;
|
|
|
|
case CaseInsensitive:
|
|
for (; i < len_c; ++i)
|
|
if (__lowercase(_p[(int)(n + i)]) != __lowercase(c[i]))
|
|
break;
|
|
break;
|
|
}
|
|
|
|
if (i == len_c)
|
|
return &_p[(int)n]; // match
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
String String::Left(uint n) const
|
|
{ return String(_p, _p + Types::Min(n, Len())); }
|
|
String String::Right(uint n) const
|
|
{
|
|
const uint l = Len();
|
|
return String(_p + Types::Min(n, l), _p + l);
|
|
}
|
|
String String::Mid(uint p, int n) const
|
|
{
|
|
uint l = Len();
|
|
p = Types::Min(p, l);
|
|
|
|
int char_left = int(l) - int(p);
|
|
n = (n < 0) || (n > char_left) ? char_left : n;
|
|
|
|
return String(_p + p, _p + p + n);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String String::Format(const char *format, ...)
|
|
{
|
|
char static_buffer[4096];
|
|
|
|
va_list varg;
|
|
va_start(varg, format);
|
|
vsnprintf(static_buffer, 4095, format, varg);
|
|
va_end(varg);
|
|
|
|
return String(static_buffer);
|
|
}
|
|
String String::operator + (const char *_ptr) const
|
|
{
|
|
String _s = *this;
|
|
_s += _ptr;
|
|
return _s;
|
|
}
|
|
String String::operator + (const String &s) const
|
|
{
|
|
String _s = *this;
|
|
_s += s.c_str();
|
|
return _s;
|
|
}
|
|
void String::operator += (const char *_ptr)
|
|
{
|
|
if (!_ptr)
|
|
return;
|
|
|
|
if (_p.IsNull())
|
|
*this = _ptr;
|
|
|
|
else
|
|
{
|
|
size_t in_len = strlen(_ptr), total_len = Len() + in_len;
|
|
|
|
if ((total_len + 1) <= _p.GetCount())
|
|
{
|
|
memcpy(&_p[Len()], _ptr, in_len);
|
|
_p[(int)total_len] = 0;
|
|
Touch();
|
|
}
|
|
else
|
|
{
|
|
Array <char> n_p(total_len + 1, Alloc::StringBuffer);
|
|
|
|
if (n_p.IsValid())
|
|
{
|
|
memcpy(&n_p[0], c_str(), Len());
|
|
memcpy(&n_p[Len()], _ptr, in_len);
|
|
n_p[(int)total_len] = 0;
|
|
|
|
_p.Transfer(n_p);
|
|
Touch();
|
|
}
|
|
else
|
|
Clear();
|
|
}
|
|
}
|
|
}
|
|
void String::operator += (float v)
|
|
{
|
|
char tmp[32];
|
|
_snprintf(tmp, 31, "%.4f", v);
|
|
*this += tmp;
|
|
}
|
|
void String::operator += (double v)
|
|
{
|
|
char tmp[32];
|
|
_snprintf(tmp, 31, "%.4f", v);
|
|
*this += tmp;
|
|
}
|
|
void String::operator += (int v)
|
|
{
|
|
char tmp[32];
|
|
_snprintf(tmp, 31, "%d", v);
|
|
*this += tmp;
|
|
}
|
|
void String::operator += (uint v)
|
|
{
|
|
char tmp[32];
|
|
_snprintf(tmp, 31, "%d", (int)v);
|
|
*this += tmp;
|
|
}
|
|
void String::operator += (bool v)
|
|
{ *this += v ? "true" : "false"; }
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String &String::operator = (const String &b)
|
|
{
|
|
if (b.IsEmpty())
|
|
Clear();
|
|
else
|
|
{
|
|
Set(b.c_str(), b.c_str() + b.Len());
|
|
hash = b.hash;
|
|
len = b.len;
|
|
}
|
|
return *this;
|
|
}
|
|
String &String::operator = (const char *s)
|
|
{
|
|
Set(s);
|
|
return *this;
|
|
}
|
|
void String::Set(const char *s, const char *e)
|
|
{
|
|
// TODO properly handle copying one part of a string to itself.
|
|
if (s == _p)
|
|
return;
|
|
|
|
size_t new_len = s ? (e ? e - s : strlen(s)) : 0;
|
|
|
|
if (new_len > 0)
|
|
{
|
|
if ((new_len + 1) > _p.GetCount())
|
|
{
|
|
Clear();
|
|
_p.Allocate(new_len + 8); // Note: +8 to help reduce allocator pressure on string concatenation.
|
|
}
|
|
|
|
if (_p.IsValid())
|
|
{
|
|
memcpy(_p, s, new_len);
|
|
_p[(int)new_len] = 0;
|
|
Touch();
|
|
}
|
|
else
|
|
Clear();
|
|
}
|
|
else
|
|
Clear();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String &String::operator << (const String &v)
|
|
{ *this += v; return *this; }
|
|
String &String::operator << (const char *v)
|
|
{ *this += v; return *this; }
|
|
String &String::operator << (float v)
|
|
{ *this += v; return *this; }
|
|
String &String::operator << (double v)
|
|
{ *this += v; return *this; }
|
|
String &String::operator << (int v)
|
|
{ *this += v; return *this; }
|
|
String &String::operator << (uint v)
|
|
{ *this += v; return *this; }
|
|
String &String::operator << (bool v)
|
|
{ *this += v ? "true" : "false"; return *this; }
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
int String::atoh(const char *_sr)
|
|
{
|
|
ushort p = 0, s = 0;
|
|
int v = 0;
|
|
char str[32];
|
|
|
|
strncpy(str, _sr, 8);
|
|
str[8] = 0;
|
|
|
|
strlwr(str);
|
|
|
|
while (str[p])
|
|
{
|
|
if ((str[p] == 'x') || (str[p] == 'h'))
|
|
{
|
|
s = (ushort)(p + 1);
|
|
break;
|
|
}
|
|
p++;
|
|
}
|
|
while (str[s])
|
|
{
|
|
v *= 16;
|
|
if ((str[s] >= '0') && (str[s] <= '9'))
|
|
v += str[s] - '0';
|
|
else if ((str[s] >= 'a') && (str[s] <= 'f'))
|
|
v += str[s] - 'a' + 10;
|
|
s++;
|
|
}
|
|
return v;
|
|
}
|
|
int String::atoi(const char *p)
|
|
{
|
|
char sign = 1;
|
|
if (p[0] == '-')
|
|
{
|
|
sign = -1;
|
|
p++;
|
|
}
|
|
int integer = 0;
|
|
while ((p[0] >= '0') && (p[0] <= '9'))
|
|
{
|
|
integer *= 10;
|
|
integer += *p++ - '0';
|
|
}
|
|
return (integer * sign);
|
|
}
|
|
float String::atof(const char *s, const char *e, bool support_comma)
|
|
{
|
|
if (!s)
|
|
return 0.f;
|
|
if (!e)
|
|
e = s + strlen(s);
|
|
|
|
float sign = 1;
|
|
if (s == e)
|
|
return 0.f;
|
|
if (s[0] == '-')
|
|
{
|
|
sign = -1;
|
|
s++;
|
|
}
|
|
if (s == e)
|
|
return -0.f;
|
|
|
|
float tt = 1.f;
|
|
float real = 0.f;
|
|
float integer = 0.f;
|
|
|
|
while ((s < e) && (s[0] != '.') && ((s[0] != ',') || !support_comma))
|
|
{
|
|
if (s[0] == 'e')
|
|
goto exponent;
|
|
if ((s[0] < '0') || (s[0] > '9'))
|
|
return (float)(integer * sign);
|
|
|
|
integer *= 10.f;
|
|
integer += *s++ - '0';
|
|
}
|
|
if (s == e)
|
|
return (float)(integer * sign);
|
|
|
|
s++;
|
|
while ((s < e) && (s[0] != 'e'))
|
|
{
|
|
if ((s[0] != '-') && ((s[0] < '0') || (s[0] > '9')))
|
|
return sign * (integer + (float)real / tt);
|
|
real *= 10.f;
|
|
real += *s++ - '0';
|
|
tt *= 10.f;
|
|
}
|
|
if (s == e)
|
|
return sign * (integer + real / tt);
|
|
|
|
exponent:;
|
|
|
|
s++;
|
|
if (s == e)
|
|
return (float)(integer * sign);
|
|
|
|
int expsign = (*s++ == '-') ? -1 : 1;
|
|
|
|
if (s == e)
|
|
return (float)(integer * sign);
|
|
|
|
uint exp = 0;
|
|
float mulexp = 1;
|
|
|
|
while ((s < e) && (s[0] >= '0') && (s[0] <= '9'))
|
|
{
|
|
exp *= 10;
|
|
exp += *s++ - '0';
|
|
}
|
|
for (uint expc = 0; expc < exp; expc++)
|
|
mulexp *= 10.f;
|
|
|
|
if (expsign == -1)
|
|
return (float)(sign * (integer + (float)real / tt)) / mulexp;
|
|
return (float)(sign * (integer + (float)real / tt)) * mulexp;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::FileCleanName()
|
|
{
|
|
if (!_p)
|
|
return false;
|
|
|
|
char *f = _p;
|
|
Array <char> n(Len() + 1, Alloc::StringBuffer);
|
|
if (n.IsNull())
|
|
return false;
|
|
|
|
// Remove drive letter case under the Windows platform.
|
|
#if __PLATFORM_WINDOWS__
|
|
if ((Len() > 2) && (f[1] == ':'))
|
|
if ((f[0] >= 'A') && (f[0] <= 'Z'))
|
|
f[0] += 'a' - 'A';
|
|
#endif
|
|
|
|
// Convert directory separator from backslash to forward slash.
|
|
char *c = n;
|
|
for (; f[0]; ++f)
|
|
if (f[0] == '\\')
|
|
f[0] = '/';
|
|
|
|
// Remove redundant forward slashes.
|
|
f = _p;
|
|
while (f[0])
|
|
{
|
|
if (f[0] == '/')
|
|
{
|
|
f++;
|
|
while (f[0] && (f[0] == '/'))
|
|
f++;
|
|
*c++ = '/';
|
|
}
|
|
else
|
|
*c++ = *f++;
|
|
}
|
|
c[0] = 0;
|
|
|
|
// Replace illegal characters.
|
|
f = n;
|
|
#if __PLATFORM_WINDOWS__
|
|
if (!strcmp(f, "file:/"))
|
|
f += 6;
|
|
#endif
|
|
|
|
if (f[0] == '@') // Allow internal mount points.
|
|
f++;
|
|
|
|
for (int i = 0; f[i]; )
|
|
{
|
|
uint char_size = GetUtf8CharSize((const uchar *)&f[i]);
|
|
|
|
if (char_size == 1)
|
|
if (
|
|
// Allowed ranges.
|
|
!((f[i] >= 'a') && (f[i] <= 'z')) &&
|
|
!((f[i] >= 'A') && (f[i] <= 'Z')) &&
|
|
!((f[i] >= '0') && (f[i] <= '9')) &&
|
|
|
|
// Allowed characters.
|
|
(f[i] != ' ') && (f[i] != '.') && (f[i] != '_') && (f[i] != '/') &&
|
|
(f[i] != '-') && (f[i] != '!') && (f[i] != '+') && (f[i] != '(') &&
|
|
(f[i] != ')') && (f[i] != '#') && (f[i] != '\'') && (f[i] != '&')
|
|
|
|
// Windows drive letter.
|
|
#if __PLATFORM_WINDOWS__
|
|
&& !((f[i] == ':') && (i == 1))
|
|
#endif
|
|
)
|
|
f[i] = '_';
|
|
|
|
i += char_size;
|
|
}
|
|
|
|
// Now commit.
|
|
Clear();
|
|
_p = n;
|
|
Touch();
|
|
return true;
|
|
}
|
|
String String::FileGetExtension(const char *path)
|
|
{
|
|
if (!path)
|
|
return "";
|
|
|
|
int n = (int)strlen(path);
|
|
while (path[n] != '.' && (n >= 0))
|
|
n--;
|
|
|
|
return n < 0 ? "" : &path[n + 1];
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
size_t String::strlen(const char *s)
|
|
{
|
|
size_t n = 0;
|
|
if (s)
|
|
for ( ; s[n]; ++n);
|
|
return n;
|
|
}
|
|
bool String::strccmp(const char *sa, const char *sb)
|
|
{
|
|
if (!sa || !sb)
|
|
return false;
|
|
|
|
while (sa[0] && sb[0])
|
|
if (*sa++ != *sb++)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
char *String::strfindchar(const char *f, const char c, const char *t)
|
|
{
|
|
if (t)
|
|
while (f[0] && (f[0] != c) && (f < t))
|
|
f++;
|
|
else
|
|
while (f[0] && (f[0] != c))
|
|
f++;
|
|
|
|
return const_cast <char *> (f);
|
|
}
|
|
void String::rmname(char *s)
|
|
{
|
|
if (!s)
|
|
return;
|
|
int n = (int)strlen(s) - 1;
|
|
while ((n >= 0) && (s[n] != ':') && (s[n] != '\\') && (s[n] != '/'))
|
|
n--;
|
|
if (n < 0)
|
|
s[0] = 0;
|
|
else
|
|
if (s[n + 1])
|
|
s[n + 1] = 0;
|
|
}
|
|
|
|
void String::rmpath(char *s)
|
|
{
|
|
String tmp(s);
|
|
|
|
int n = (int)tmp.Len();
|
|
const char *p = tmp.c_str();
|
|
if (!p)
|
|
return;
|
|
|
|
while ((n >= 0) && (p[n] != '\\') && (p[n] != '/'))
|
|
n--;
|
|
if (n < 0)
|
|
return;
|
|
|
|
strcpy(s, &p[n+1]);
|
|
}
|
|
void String::rmext(char *s)
|
|
{
|
|
if (!s)
|
|
return;
|
|
|
|
int n = (int)strlen(s) - 1;
|
|
while ((n >= 0) && (s[n] != '.'))
|
|
{
|
|
if ((s[n] == '\\') || (s[n] == '/') || (s[n] == ':'))
|
|
break;
|
|
n--;
|
|
}
|
|
if (n < 0)
|
|
return;
|
|
s[n] = 0;
|
|
}
|
|
void String::rmpathext(char *s)
|
|
{
|
|
rmpath(s);
|
|
rmext(s);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::Allocate(uint size)
|
|
{
|
|
Touch();
|
|
if (!_p.Allocate(size + 1))
|
|
return false;
|
|
|
|
Memory::Set(_p.c_ptr(), 0, _p.GetSize());
|
|
return true;
|
|
}
|
|
void String::Touch()
|
|
{
|
|
len = -1;
|
|
hash = 0;
|
|
}
|
|
void String::Clear()
|
|
{
|
|
_p.Free();
|
|
Touch();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::equals(const char *s, const uint hash) const
|
|
{
|
|
if (hash && (hash != Hash()))
|
|
return false;
|
|
if (_p == s)
|
|
return true;
|
|
return (_p && !strcmp(s, _p)) ? true : false;
|
|
}
|
|
bool String::operator == (const String &b) const
|
|
{ return b.IsEmpty() ? IsEmpty() : equals(b, b.Hash()); }
|
|
bool String::operator == (const char *s) const
|
|
{ return s ? equals(s) : IsEmpty(); }
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool String::operator != (const String &b) const
|
|
{ return !(*this == b); }
|
|
bool String::operator != (const char *s) const
|
|
{ return !(*this == s); }
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
void String::operator += (const String &b)
|
|
{
|
|
if (!b.IsEmpty())
|
|
*this += b.c_str();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
uint String::strhash(const char *s)
|
|
{
|
|
uint hash = 0;
|
|
|
|
if (s)
|
|
while (s[0])
|
|
{
|
|
hash = hash * 37 + s[0];
|
|
s++;
|
|
}
|
|
|
|
hash |= 1;
|
|
return hash;
|
|
}
|
|
uint String::Hash() const
|
|
{
|
|
if (hash == 0)
|
|
hash = strhash(_p);
|
|
return hash;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
void String::FileCutPath()
|
|
{ rmpath(_p); Touch(); }
|
|
void String::FileSwapExtension(const char *ext)
|
|
{ rmext(_p); Touch(); *this += ext; }
|
|
void String::FileCutExtension()
|
|
{ rmext(_p); Touch(); }
|
|
void String::FileCutPathAndExtension()
|
|
{ rmpathext(_p); Touch(); }
|
|
void String::FileCutName()
|
|
{ rmname(_p); Touch(); }
|
|
//------------------------------------------------------------------------------
|