|
|
// Copyright (c) 1997-1999 Microsoft Corporation
//
// string class
//
// 8-14-97 sburns
#include "headers.hxx"
String::String(PCSTR lpsz) : base() { // ISSUE-2002/03/06-sburns consider strsafe function
size_t len = lpsz ? static_cast<size_t>(lstrlenA(lpsz)) : 0;
if (len) { assignFromAnsi(lpsz, len); } }
String::String(const AnsiString& s) : base() { size_t len = s.length();
if (len) { assignFromAnsi(s.data(), len); } }
void String::assignFromAnsi(PCSTR lpsz, size_t len) { ASSERT(lpsz); ASSERT(len);
// add 1 to allow for trailing null
wchar_t* buf = new wchar_t[len + 1];
// REVIEWED-2002/03/06-sburns correct byte count passed
::ZeroMemory(buf, (len + 1) * sizeof wchar_t);
size_t result = static_cast<size_t>(
// REVIEWED-2002/03/06-sburns correct byte/char counts
::MultiByteToWideChar( CP_ACP, 0, lpsz,
// len bytes in the source ansi string (not incl trailing null)
static_cast<int>(len), buf,
// len characters in the result wide string (not incl trailing
// null)
static_cast<int>(len)));
if (result) { ASSERT(result <= len); assign(buf); }
delete[] buf; }
HRESULT String::as_OLESTR(LPOLESTR& oleString) const { size_t len = length();
oleString = reinterpret_cast<LPOLESTR>( ::CoTaskMemAlloc((len + 1) * sizeof(wchar_t))); if (oleString) { copy(oleString, len); oleString[len] = 0; return S_OK; }
return E_OUTOFMEMORY; }
String String::load(unsigned resID, HINSTANCE hInstance) { if (!hInstance) { hInstance = GetResourceModuleHandle(); }
#ifdef DBG
// pick a silly small buffer size to make sure our resize logic gets
// exercised.
static const int TEMP_LEN = 7; #else
static const int TEMP_LEN = 512; #endif
wchar_t temp[TEMP_LEN];
// ISSUE-2002/02/25-sburns why is tempLen needed? just use TEMP_LEN?
int tempLen = TEMP_LEN;
// REVIEWED-2002/03/06-sburns correct character count passed
int len = ::LoadString(hInstance, resID, temp, tempLen);
// we expect that if the caller is loading a string, the string is non-
// empty. An empty string probably indicates something is broken in the
// caller's program. It is legal, but silly, to put empty strings in a
// resource table.
ASSERT(len);
if (len == 0) { return String(); }
if (tempLen - len > 1) { // the string fit into the temp buffer with at least 1 character to
// spare. If the load failed, len == 0, and we return the empty
// string.
return String(temp); }
// the string did not fit. Try larger buffer sizes until the string does
// fit with at least 1 character to spare
int newLen = tempLen; wchar_t* newTemp = 0; do { delete[] newTemp; newLen += TEMP_LEN; newTemp = new wchar_t[static_cast<size_t>(newLen)];
// REVIEWED-2002/03/06-sburns correct character count passed
len = ::LoadString(hInstance, resID, newTemp, newLen); }
// ISSUE-2002/02/25-sburns growth is unbounded here...
while (newLen - len <= 1); // repeat until at least 1 char to spare
String r(newTemp); delete[] newTemp; return r; }
String& String::replace(const String& from, const String& to) { if (from.empty()) { return *this; }
_copy(); String::size_type i = 0; String::size_type fl = from.length(); String::size_type tl = to.length(); String::size_type len = length(); const wchar_t* td = to.data();
do { i = find(from, i); if (i == String::npos) { return *this; } base::replace(i, fl, td, tl); i += tl; } while (i <= len);
return *this; }
String& String::replace_each_of(const String& from, const String& to) { if (from.empty()) { return *this; }
_copy(); String::size_type i = 0; String::size_type fl = from.length(); String::size_type tl = to.length(); String::size_type len = length(); const wchar_t* td = to.data();
do { i = find_first_of(from, i); if (i == String::npos) { return *this; } base::replace(i, 1, td, tl); i += tl; } while (i <= len);
return *this; }
String& String::strip(StripType type, wchar_t charToStrip) { String::size_type start = 0; String::size_type stop = length(); const wchar_t* p = data();
if (type & LEADING) { while (start < stop && p[start] == charToStrip) { ++start; } }
if (type & TRAILING) { while (start < stop && p[stop - 1] == charToStrip) { --stop; } }
if (stop == start) { assign(String()); } else { // this goofiness due to a bug in basic_string where you can't assign
// a piece of yourself, because you delete yourself before you copy!
// assign(p + start, stop - start);
String s(p + start, stop - start); assign(s); }
return *this; }
String& String::to_lower() { if (length()) { _copy();
// ISSUE-2002/03/06-sburns consider strsafe function
_wcslwr(const_cast<wchar_t*>(c_str())); } return *this; }
String& String::to_upper() { if (length()) { _copy();
// ISSUE-2002/03/06-sburns consider strsafe function
_wcsupr(const_cast<wchar_t*>(c_str())); } return *this; }
void String::_copy() { size_type len = length();
if (len) { value_type* buf = new value_type[len + 1]; copy(buf, len); buf[len] = 0; assign(buf); delete[] buf; } }
//
// static functions
//
#if defined(ALPHA) || defined(IA64)
String __cdecl String::format( const String& qqfmt, ...) #else
// the x86 compiler won't allow the first parameter to be a reference
// type. This is a compiler bug.
String __cdecl String::format( const String qqfmt, ...) #endif
{ // ISSUE-2002/03/06-sburns should assert that qqfmt is not empty (I'd
// just add it now, but I'm not entirely confident it wouldn't hose the
// varargs
// ASSERT(!qqfmt.empty());
String result;
va_list argList; va_start(argList, qqfmt);
PTSTR temp = 0; PCTSTR f = qqfmt.c_str();
if ( // REVIEWED-2002/03/06-sburns no char/byte or buffer size issues: we ask
// the API to allocate for us.
// REVIEWED-2002/03/29-sburns no unbounded allocation error here.
// If I pass nSize = 0 and FORMAT_MESSAGE_ALLOCATE_BUFFER in dwFlags,
// the max result size is 32K - 1 characters. Looking at the code in
// message.c, it looks like the reserve space is whatever the user asked
// as a maximum rounded up to the nearest 64K. That makes sense given my
// test, since 32K chars = 64K bytes. Experimentally, even if I ask for
// a max buffer size > 0x87FFF chars, it looks like the most I can get
// is 0x87FFE chars.
::FormatMessage( FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, f, 0, 0, reinterpret_cast<PTSTR>(&temp), 0, &argList)) { result = temp; ::LocalFree(temp); }
va_end(argList); return result; }
String __cdecl String::format( const wchar_t* qqfmt, ...) { ASSERT(qqfmt);
String result;
va_list argList; va_start(argList, qqfmt);
PTSTR temp = 0;
if ( // REVIEWED-2002/03/06-sburns no char/byte or buffer size issues: we ask
// the API to allocate for us.
::FormatMessage( FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, qqfmt, 0, 0, reinterpret_cast<PTSTR>(&temp), 0, &argList)) { result = temp; ::LocalFree(temp); }
va_end(argList); return result; }
String __cdecl String::format(unsigned formatResID, ...) { String fmt = String::load(formatResID); String result;
va_list argList; va_start(argList, formatResID); PTSTR temp = 0; if ( // REVIEWED-2002/03/06-sburns no char/byte or buffer size issues: we ask
// the API to allocate for us.
// REVIEWED-2002/03/29-sburns no unbounded allocation error here.
// If I pass nSize = 0 and FORMAT_MESSAGE_ALLOCATE_BUFFER in dwFlags,
// the max result size is 32K - 1 characters. Looking at the code in
// message.c, it looks like the reserve space is whatever the user asked
// as a maximum rounded up to the nearest 64K. That makes sense given my
// test, since 32K chars = 64K bytes. Experimentally, even if I ask for
// a max buffer size > 0x87FFF chars, it looks like the most I can get
// is 0x87FFE chars.
::FormatMessage( FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, fmt.c_str(), 0, 0, reinterpret_cast<PTSTR>(&temp), 0, &argList)) { result = temp; ::LocalFree(temp); }
va_end(argList); return result; }
String __cdecl String::format(int formatResID, ...) { String fmt = String::load(formatResID);
va_list argList; va_start(argList, formatResID); PTSTR temp = 0; if ( // REVIEWED-2002/03/06-sburns no char/byte or buffer size issues: we ask
// the API to allocate for us.
// REVIEWED-2002/03/29-sburns no unbounded allocation error here.
// If I pass nSize = 0 and FORMAT_MESSAGE_ALLOCATE_BUFFER in dwFlags,
// the max result size is 32K - 1 characters. Looking at the code in
// message.c, it looks like the reserve space is whatever the user asked
// as a maximum rounded up to the nearest 64K. That makes sense given my
// test, since 32K chars = 64K bytes. Experimentally, even if I ask for
// a max buffer size > 0x87FFF chars, it looks like the most I can get
// is 0x87FFE chars.
::FormatMessage( FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, fmt.c_str(), 0, 0, reinterpret_cast<PTSTR>(&temp), 0, &argList)) { String retval(temp); ::LocalFree(temp); va_end(argList); return retval; }
va_end(argList); return String(); }
int String::icompare(const String& str) const { int i = ::CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE
// these flags necessary for Japanese strings
| NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH, c_str(), static_cast<int>(length()), str.c_str(), static_cast<int>(str.length())); if (i) { // convert to < 0, == 0, > 0 C runtime convention
return i - 2; }
// this will be wrong, but what option do we have?
return i; }
HRESULT WideCharToMultiByteHelper( UINT codePage, DWORD flags, const String& str, char* buffer, size_t bufferSizeInBytes, size_t& result) { ASSERT(!str.empty());
result = 0;
HRESULT hr = S_OK;
int r =
// REVIEWED-2002/03/06-sburns correct character/byte counts passed.
::WideCharToMultiByte( codePage, flags, str.c_str(),
// the character count count
static_cast<int>(str.length()), buffer,
// the buffer size in bytes
static_cast<int>(bufferSizeInBytes), 0, 0); if (!r) { hr = Win32ToHresult(::GetLastError()); }
ASSERT(SUCCEEDED(hr));
result = static_cast<size_t>(r);
return hr; }
String::ConvertResult String::convert(AnsiString& ansi, UINT codePage) const { ansi.erase();
ConvertResult result = CONVERT_FAILED;
do { if (empty()) { // nothing else to do.
result = CONVERT_SUCCESSFUL; break; }
// determine the size of the buffer required to hold the ANSI string
const wchar_t* wide = c_str();
size_t bufferSizeInBytes = 0; HRESULT hr = ::WideCharToMultiByteHelper( codePage, 0, wide, 0, 0,
// REVIEWED-2002/03/06-sburns correct byte count passed.
bufferSizeInBytes); BREAK_ON_FAILED_HRESULT(hr); if (bufferSizeInBytes > 0) { // +1 for extra null-termination paranoia
AnsiString a(bufferSizeInBytes + 1, 0); char* p = const_cast<char*>(a.c_str());
size_t r = 0;
hr = ::WideCharToMultiByteHelper( codePage, 0, wide, p,
// REVIEWED-2002/03/06-sburns correct byte count passed.
bufferSizeInBytes, r); BREAK_ON_FAILED_HRESULT(hr);
ansi = a; result = CONVERT_SUCCESSFUL; } } while (0);
return result; }
template<class UnsignedType> class UnsignedConvertHelper { public:
// at first glance, one might think that this is a job for a template
// member function. That's what I thought. Unfortunately, the combination
// of freely convertible integer types and the binding rules for resolving
// function templates results in ambiguity. Using a static class method,
// though, allows the caller to specify the template parameter types, and
// avoid the abiguity.
static String::ConvertResult doit(const String& s, UnsignedType& u, int radix, UnsignedType maxval) { // call the long version, then truncate as appropriate
unsigned long ul = 0; u = 0; String::ConvertResult result = s.convert(ul, radix);
if (result == String::CONVERT_SUCCESSFUL) { if (ul <= maxval) { // ul will fit into an unsigned int.
u = static_cast<UnsignedType>(ul); } else { result = String::CONVERT_OVERFLOW; } }
return result; } };
template<class IntType> class IntegerConvertHelper { public:
static String::ConvertResult doit(const String& s, IntType& u, int radix, IntType minval, IntType maxval) { long l = 0; u = 0; String::ConvertResult result = s.convert(l, radix);
if (result == String::CONVERT_SUCCESSFUL) { if (l <= maxval) { if (l >= minval) { // l will fit into an IntType.
u = static_cast<IntType>(l); } else { result = String::CONVERT_UNDERFLOW; } } else { result = String::CONVERT_OVERFLOW; } }
return result; } };
String::ConvertResult String::convert(short& s, int radix) const { return IntegerConvertHelper<short>::doit(*this, s, radix, SHRT_MIN, SHRT_MAX); }
String::ConvertResult String::convert(int& i, int radix) const { return IntegerConvertHelper<int>::doit(*this, i, radix, INT_MIN, INT_MAX); }
String::ConvertResult String::convert(unsigned short& us, int radix) const { return UnsignedConvertHelper<unsigned short>::doit( *this, us, radix, USHRT_MAX); }
String::ConvertResult String::convert(unsigned& ui, int radix) const { return UnsignedConvertHelper<unsigned int>::doit( *this, ui, radix, UINT_MAX); }
String::ConvertResult String::convert(long& l, int radix) const { l = 0; if (radix != 0 && (radix < 2 || radix > 36)) { ASSERT(false); return CONVERT_BAD_RADIX; }
String::const_pointer begptr = c_str(); String::pointer endptr = 0; errno = 0; long result = wcstol(begptr, &endptr, radix); if (errno == ERANGE) { return result == LONG_MAX ? CONVERT_OVERFLOW : CONVERT_UNDERFLOW; } if (begptr == endptr) { // no valid characters found
return CONVERT_BAD_INPUT; } if (endptr) { if (*endptr != 0) { // the conversion stopped before the null terminator => bad
// characters in input
return CONVERT_BAD_INPUT; } } else { // I doubt this is reachable
return CONVERT_FAILED; } l = result; return CONVERT_SUCCESSFUL; }
String::ConvertResult String::convert(unsigned long& ul, int radix) const { ul = 0; if (radix != 0 && (radix < 2 || radix > 36)) { ASSERT(false); return CONVERT_BAD_RADIX; }
String::const_pointer begptr = c_str(); String::pointer endptr = 0; errno = 0; unsigned long result = wcstoul(begptr, &endptr, radix); if (errno == ERANGE) { // overflow is the only possible range error for an unsigned type.
return CONVERT_OVERFLOW; } if (begptr == endptr) { // no valid characters found
return CONVERT_BAD_INPUT; } if (endptr) { if (*endptr != 0) { // the conversion stopped before the null terminator => bad
// characters in input
return CONVERT_BAD_INPUT; } } else { // I doubt this is reachable
return CONVERT_FAILED; } ul = result; return CONVERT_SUCCESSFUL; }
String::ConvertResult String::convert(double& d) const { d = 0.0;
String::const_pointer begptr = c_str(); String::pointer endptr = 0; errno = 0; double result = wcstod(begptr, &endptr); if (errno == ERANGE) { // result is +/-HUGE_VAL on overflow, 0 on underflow.
return result ? CONVERT_OVERFLOW : CONVERT_UNDERFLOW; } if (begptr == endptr) { // no valid characters found
return CONVERT_BAD_INPUT; } if (endptr) { if (*endptr != 0) { // the conversion stopped before the null terminator => bad
// characters in input
return CONVERT_BAD_INPUT; } } else { // I doubt this is reachable
return CONVERT_FAILED; } d = result; return CONVERT_SUCCESSFUL; }
/* This was commented out because it is no longer in use. If
it is to be used it must be fixed to work with non Arabic digits
#define MAX_DECIMAL_STRING_LENGTH_FOR_LARGE_INTEGER 20
String::ConvertResult String::convert(LARGE_INTEGER& li) const { li.QuadPart = 0;
if (size() > MAX_DECIMAL_STRING_LENGTH_FOR_LARGE_INTEGER) { // string is too long
return CONVERT_OVERFLOW; }
String::const_pointer begptr = c_str(); String::const_pointer endptr = begptr; errno = 0;
BOOL bNeg = FALSE; if (*endptr == L'-') { bNeg = TRUE; endptr++; } while (*endptr != L'\0') { if (!iswctype(*endptr,_DIGIT)) { return CONVERT_BAD_INPUT; } li.QuadPart = 10 * li.QuadPart + (*endptr-L'0'); endptr++; } if (bNeg) { li.QuadPart *= -1; }
if (begptr == endptr) { // no valid characters found
li.QuadPart = 0; return CONVERT_BAD_INPUT; }
if (endptr) { if (*endptr != 0) { // the conversion stopped before the null terminator => bad
// characters in input
li.QuadPart = 0; return CONVERT_BAD_INPUT; } } else { // I doubt this is reachable
li.QuadPart = 0; return CONVERT_FAILED; } return CONVERT_SUCCESSFUL; } */
bool String::is_numeric() const { if (empty()) { return false; }
size_t len = length(); WORD* charTypeInfo = new WORD[len];
// REVIEWED-2002/03/06-sburns correct byte count passed
::ZeroMemory(charTypeInfo, len * sizeof WORD);
bool result = false;
do { BOOL success = ::GetStringTypeEx( LOCALE_USER_DEFAULT, CT_CTYPE1, c_str(), static_cast<int>(length()), charTypeInfo);
ASSERT(success);
if (!success) { break; }
// look thru the type info array, ensure that all chars are digits.
bool nonDigitFound = false; for (size_t i = 0; i < len; ++i) { // We only consider decimal digits, not C2_EUROPENUMBER and
// C2_ARABICNUMBER. I wonder if that is correct?
if (!(charTypeInfo[i] & C1_DIGIT)) { nonDigitFound = true; break; } }
// a string is numeric if no non-digit characters are found.
result = !nonDigitFound; } while (0);
delete[] charTypeInfo; charTypeInfo = 0;
return result; }
|