// 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(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( // 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(len), buf, // len characters in the result wide string (not incl trailing // null) static_cast(len))); if (result) { ASSERT(result <= len); assign(buf); } delete[] buf; } HRESULT String::as_OLESTR(LPOLESTR& oleString) const { size_t len = length(); oleString = reinterpret_cast( ::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(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(c_str())); } return *this; } String& String::to_upper() { if (length()) { _copy(); // ISSUE-2002/03/06-sburns consider strsafe function _wcsupr(const_cast(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(&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(&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(&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(&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(length()), str.c_str(), static_cast(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(str.length()), buffer, // the buffer size in bytes static_cast(bufferSizeInBytes), 0, 0); if (!r) { hr = Win32ToHresult(::GetLastError()); } ASSERT(SUCCEEDED(hr)); result = static_cast(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(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 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(ul); } else { result = String::CONVERT_OVERFLOW; } } return result; } }; template 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(l); } else { result = String::CONVERT_UNDERFLOW; } } else { result = String::CONVERT_OVERFLOW; } } return result; } }; String::ConvertResult String::convert(short& s, int radix) const { return IntegerConvertHelper::doit(*this, s, radix, SHRT_MIN, SHRT_MAX); } String::ConvertResult String::convert(int& i, int radix) const { return IntegerConvertHelper::doit(*this, i, radix, INT_MIN, INT_MAX); } String::ConvertResult String::convert(unsigned short& us, int radix) const { return UnsignedConvertHelper::doit( *this, us, radix, USHRT_MAX); } String::ConvertResult String::convert(unsigned& ui, int radix) const { return UnsignedConvertHelper::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(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; }