2703 lines
45 KiB
C++
2703 lines
45 KiB
C++
/*++
|
|
|
|
Copyright (c) 1994-1999 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
strfrn.cpp
|
|
|
|
Abstract:
|
|
|
|
String Functions
|
|
|
|
Author:
|
|
|
|
Ronald Meijer (ronaldm)
|
|
|
|
Project:
|
|
|
|
Internet Services Manager (cluster edition)
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
//
|
|
// Include Files
|
|
//
|
|
#include "stdafx.h"
|
|
#include "common.h"
|
|
#include <pudebug.h>
|
|
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char BASED_CODE THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
|
|
#define new DEBUG_NEW
|
|
|
|
|
|
#ifdef _MT
|
|
|
|
//
|
|
// Thread protected stuff
|
|
//
|
|
#define RaiseThreadProtection() EnterCriticalSection(&_csSect)
|
|
#define LowerThreadProtection() LeaveCriticalSection(&_csSect)
|
|
|
|
static CRITICAL_SECTION _csSect;
|
|
|
|
#else
|
|
|
|
#pragma message("Module is not thread-safe")
|
|
|
|
#define RaiseThreadProtection()
|
|
#define LowerThreadProtection()
|
|
|
|
#endif // _MT
|
|
|
|
#define MAKE_NULL(obj) { if (obj) delete obj, obj = NULL; }
|
|
|
|
|
|
//
|
|
// Text copy functions
|
|
//
|
|
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
|
|
|
|
BOOL
|
|
PCToUnixText(
|
|
OUT LPWSTR & lpstrDestination,
|
|
IN const CString strSource
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert CR/LF string to LF string (T String to W String). Destination
|
|
string will be allocated.
|
|
|
|
Arguments:
|
|
|
|
LPWSTR & lpstrDestination : Destination string
|
|
const CString & strSource : Source string
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure.
|
|
|
|
--*/
|
|
{
|
|
int cch = strSource.GetLength() + 1;
|
|
lpstrDestination = (LPWSTR)AllocMem(cch * sizeof(WCHAR));
|
|
|
|
if (lpstrDestination != NULL)
|
|
{
|
|
LPCTSTR lpS = strSource;
|
|
LPWSTR lpD = lpstrDestination;
|
|
|
|
do
|
|
{
|
|
if (*lpS != _T('\r'))
|
|
{
|
|
|
|
#ifdef UNICODE
|
|
*lpD++ = *lpS;
|
|
#else
|
|
::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpS, 1, lpD++, 1);
|
|
#endif // UNICODE
|
|
|
|
}
|
|
}
|
|
while (*lpS++);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
UnixToPCText(
|
|
OUT CString & strDestination,
|
|
IN LPCWSTR lpstrSource
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Expand LF to CR/LF (no allocation necessary) W String to T String.
|
|
|
|
Arguments:
|
|
|
|
CString & strDestination : Destination string
|
|
LPCWSTR lpstrSource : Source string
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure.
|
|
|
|
--*/
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
|
|
try
|
|
{
|
|
LPCWSTR lpS = lpstrSource;
|
|
|
|
//
|
|
// Since we're doubling every linefeed length, assume
|
|
// the worst possible expansion to start with.
|
|
//
|
|
int cch = (::lstrlenW(lpstrSource) + 1) * 2;
|
|
LPTSTR lpD = strDestination.GetBuffer(cch);
|
|
|
|
do
|
|
{
|
|
if (*lpS == L'\n')
|
|
{
|
|
*lpD++ = _T('\r');
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
*lpD++ = *lpS;
|
|
#else
|
|
::WideCharToMultiByte(CP_ACP, 0, lpS, 1, lpD++, 1, NULL, NULL);
|
|
#endif // UNICODE
|
|
|
|
}
|
|
while (*lpS++);
|
|
|
|
strDestination.ReleaseBuffer();
|
|
|
|
++fSuccess;
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
TRACEEOLID("Exception in UnixToPCText");
|
|
e->ReportError();
|
|
e->Delete();
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
/*
|
|
|
|
BOOL
|
|
TextToText(
|
|
OUT LPWSTR & lpstrDestination,
|
|
IN const CString & strSource
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Straight copy with allocation. T String to W String.
|
|
|
|
Arguments:
|
|
|
|
LPWSTR & lpstrDestination : Destination string
|
|
const CString & strSource : Source string
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure.
|
|
|
|
--/
|
|
{
|
|
int cch = strSource.GetLength() + 1;
|
|
lpstrDestination = (LPWSTR)AllocMem(cch * sizeof(WCHAR));
|
|
|
|
if (lpstrDestination != NULL)
|
|
{
|
|
TWSTRCPY(lpstrDestination, strSource, cch);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
#ifndef UNICODE
|
|
|
|
|
|
|
|
#define WBUFF_SIZE 255
|
|
|
|
|
|
|
|
LPWSTR
|
|
ReferenceAsWideString(
|
|
IN LPCTSTR str
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reference a T string as a W string (non-unicode only).
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR str : Source string
|
|
|
|
Return Value:
|
|
|
|
Wide char pointer to wide string.
|
|
|
|
Notes:
|
|
|
|
This uses an internal wide char buffer, which will be overwritten
|
|
by subsequent calls to this function.
|
|
|
|
--/
|
|
{
|
|
static WCHAR wchBuff[WBUFF_SIZE + 1];
|
|
|
|
::MultiByteToWideChar(
|
|
CP_ACP,
|
|
MB_PRECOMPOSED,
|
|
str,
|
|
-1,
|
|
wchBuff,
|
|
WBUFF_SIZE + 1
|
|
);
|
|
|
|
return wchBuff;
|
|
}
|
|
|
|
|
|
|
|
#endif !UNICODE
|
|
|
|
|
|
*/
|
|
|
|
|
|
LPSTR
|
|
AllocAnsiString(
|
|
IN LPCTSTR lpString
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert the wide string to an ansi (multi-byte) string, which is allocated
|
|
by this function
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lpString : Input wide string
|
|
|
|
Return Value:
|
|
|
|
Pointer to the allocated string
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Character counts are DBCS friendly
|
|
//
|
|
int cChars = lstrlen(lpString);
|
|
int nLength = (cChars * 2) + 1;
|
|
LPSTR lp = (LPSTR)AllocMem(nLength);
|
|
|
|
if (lp)
|
|
{
|
|
::WideCharToMultiByte(
|
|
CP_ACP,
|
|
0,
|
|
lpString,
|
|
cChars + 1,
|
|
lp,
|
|
nLength,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
return lp;
|
|
}
|
|
|
|
|
|
|
|
|
|
LPTSTR
|
|
AllocString(
|
|
IN LPCTSTR lpString,
|
|
IN int nLen
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocate and copy string
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lpString : Input string
|
|
int nLen : Length or -1 to autodetermine
|
|
|
|
Return Value:
|
|
|
|
Pointer to the allocated string
|
|
|
|
--*/
|
|
{
|
|
if (nLen < 0)
|
|
{
|
|
nLen = lstrlen(lpString);
|
|
}
|
|
|
|
LPTSTR lp = (LPTSTR)AllocMem((nLen + 1) * sizeof(TCHAR));
|
|
|
|
if (lp)
|
|
{
|
|
lstrcpy(lp, lpString);
|
|
}
|
|
|
|
return lp;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
IsUNCName(
|
|
IN const CString & strDirPath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if the given string path is a UNC path.
|
|
|
|
Arguments:
|
|
|
|
const CString & strDirPath : Directory path string
|
|
|
|
Return Value:
|
|
|
|
TRUE if the path is a UNC path, FALSE otherwise.
|
|
|
|
Notes:
|
|
|
|
Any string of the form \\foo\bar\whatever is considered a UNC path,
|
|
with the exception of \\.\device paths. No validation for the
|
|
existance occurs, only for the correct format.
|
|
|
|
--*/
|
|
{
|
|
if (strDirPath.GetLength() >= 5) // It must be at least as long as \\x\y,
|
|
{ //
|
|
LPCTSTR lp = strDirPath; //
|
|
if (*lp == _T('\\') // It must begin with \\,
|
|
&& *(lp + 1) == _T('\\') //
|
|
&& *(lp + 2) != _T('.') // This is a device.
|
|
&& _tcschr(lp + 3, _T('\\')) // And have at least one more \ after that
|
|
)
|
|
{
|
|
//
|
|
// Yes, it's a UNC path
|
|
//
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// No, it's not
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
IsDevicePath(
|
|
IN const CString & strDirPath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if the given path is of the form "\\.\foobar"
|
|
|
|
Arguments:
|
|
|
|
const CString & strDirPath : Directory path string
|
|
|
|
Return Value:
|
|
|
|
TRUE if the path given is a device path,
|
|
FALSE if it is not.
|
|
|
|
--*/
|
|
{
|
|
LPCTSTR lpszDevice = _T("\\\\.\\");
|
|
|
|
return _tcsnccmp(strDirPath, lpszDevice, lstrlen(lpszDevice)) == 0;
|
|
}
|
|
|
|
|
|
BOOL
|
|
PathIsValid(LPCTSTR path)
|
|
{
|
|
LPCTSTR p = path;
|
|
BOOL rc = TRUE;
|
|
if (p == NULL || *p == 0)
|
|
return FALSE;
|
|
while (*p != 0)
|
|
{
|
|
switch (*p)
|
|
{
|
|
case TEXT('|'):
|
|
case TEXT('>'):
|
|
case TEXT('<'):
|
|
case TEXT('/'):
|
|
case TEXT('?'):
|
|
case TEXT('*'):
|
|
// case TEXT(';'):
|
|
// case TEXT(','):
|
|
case TEXT('"'):
|
|
rc = FALSE;
|
|
break;
|
|
default:
|
|
if (*p < TEXT(' '))
|
|
{
|
|
rc = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
if (!rc)
|
|
{
|
|
break;
|
|
}
|
|
p++;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsFullyQualifiedPath(
|
|
IN const CString & strDirPath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if the given string is a fully qualified path name
|
|
|
|
Arguments:
|
|
|
|
const CString & strDirPath : Directory path string
|
|
|
|
Return Value:
|
|
|
|
TRUE if the path is a fully qualified path name
|
|
|
|
|
|
--*/
|
|
{
|
|
return strDirPath.GetLength() >= 3
|
|
&& strDirPath[1] == _T(':')
|
|
&& strDirPath[2] == _T('\\');
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
IsNetworkPath(
|
|
IN const CString & strDirPath,
|
|
OUT CString * pstrDrive, OPTIONAL
|
|
OUT CString * pstrUNC OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if the path exists on a network directory
|
|
in the context of the local machine.
|
|
|
|
Arguments:
|
|
|
|
const CString & strDirPath : Directory path string
|
|
CString * pstrDrive : Returns drive
|
|
CString * pstrUNC : Returns UNC path
|
|
|
|
Return Value:
|
|
|
|
TRUE if the path is a network path, FALSE if it is local.
|
|
|
|
Notes:
|
|
|
|
Only fully qualified paths with drive letters are checked.
|
|
|
|
--*/
|
|
{
|
|
BOOL fUNC = FALSE;
|
|
CString strDrive;
|
|
|
|
try
|
|
{
|
|
if (pstrDrive == NULL)
|
|
{
|
|
pstrDrive = &strDrive;
|
|
}
|
|
|
|
ASSERT(strDirPath[1] == _T(':'));
|
|
|
|
if (strDirPath[1] == _T(':'))
|
|
{
|
|
*pstrDrive = _T("?:");
|
|
|
|
//
|
|
// Fill in actual drive letter
|
|
//
|
|
pstrDrive->SetAt(0, strDirPath[0]);
|
|
UINT nType = GetDriveType(*pstrDrive);
|
|
|
|
//
|
|
// DRIVE_NO_ROOT_DIR? this is a little dodgy, but
|
|
// this is the result I get back after browsing
|
|
// using a file open dialog. Weird.
|
|
//
|
|
fUNC = (nType == DRIVE_NO_ROOT_DIR || nType == DRIVE_REMOTE);
|
|
}
|
|
|
|
//
|
|
// Return UNC path if requested
|
|
//
|
|
if (fUNC && pstrUNC != NULL)
|
|
{
|
|
DWORD dwSize = _MAX_PATH;
|
|
LPTSTR lp = pstrUNC->GetBuffer(dwSize);
|
|
::WNetGetConnection(*pstrDrive, lp, &dwSize);
|
|
pstrUNC->ReleaseBuffer();
|
|
}
|
|
}
|
|
catch(CException * e)
|
|
{
|
|
e->ReportError();
|
|
e->Delete();
|
|
}
|
|
|
|
return fUNC;
|
|
}
|
|
|
|
|
|
|
|
LPCTSTR
|
|
MakeUNCPath(
|
|
IN OUT CString & strDir,
|
|
IN LPCTSTR lpszOwner,
|
|
IN LPCTSTR lpszDirectory
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert the given directory to a UNC path.
|
|
|
|
Arguments:
|
|
|
|
CString & strDir : UNC String.
|
|
LPCTSTR lpszOwner : Computer name
|
|
LPCTSTR lpszDirectory : Source string
|
|
|
|
Return Value:
|
|
|
|
Pointer to strDir
|
|
|
|
Notes:
|
|
|
|
The owner may or may not start with "\\". If it doesn't, the
|
|
backslashes are provided.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Try to make make a unc path out of the directory
|
|
//
|
|
ASSERT(lpszDirectory[1] == _T(':'));
|
|
|
|
strDir.Format(
|
|
_T("\\\\%s\\%c$\\%s"),
|
|
PURE_COMPUTER_NAME(lpszOwner),
|
|
lpszDirectory[0],
|
|
lpszDirectory + 3
|
|
);
|
|
|
|
return (LPCTSTR)strDir;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
IsURLName(
|
|
IN const CString & strDirPath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if the given string path is an URL path.
|
|
|
|
Arguments:
|
|
|
|
const CString & strDirPath : Directory path string
|
|
|
|
Return Value:
|
|
|
|
TRUE if the path is an URL path, FALSE otherwise.
|
|
|
|
Notes:
|
|
|
|
Any string of the form protocol://whatever is considered an URL path
|
|
|
|
--*/
|
|
{
|
|
if (strDirPath.GetLength() >= 4) // It must be at least as long as x://
|
|
{ //
|
|
if (strDirPath.Find(_T("://")) > 0) // Must contain ://
|
|
{
|
|
//
|
|
// Yes, it's an URL path
|
|
//
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// No, it's not
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
CStringFindNoCase(
|
|
IN const CString & strSrc,
|
|
IN LPCTSTR lpszSub
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This should be CString::FindNoCase(). Same as CString::Find(),
|
|
but case-insensitive.
|
|
|
|
Arguments:
|
|
|
|
const CString & strSrc : Source string
|
|
LPCTSTR lpszSub : String to look for.
|
|
|
|
Return Value:
|
|
|
|
The position of the substring, or -1 if not found.
|
|
|
|
--*/
|
|
{
|
|
LPCTSTR lp1 = strSrc;
|
|
LPCTSTR lp2, lp3;
|
|
int nPos = -1;
|
|
|
|
while (*lp1)
|
|
{
|
|
lp2 = lp1;
|
|
lp3 = lpszSub;
|
|
|
|
while(*lp2 && *lp3 && _totupper(*lp2) == _totupper(*lp3))
|
|
{
|
|
++lp2;
|
|
++lp3;
|
|
}
|
|
|
|
if (!*lp3)
|
|
{
|
|
//
|
|
// Found the substring
|
|
//
|
|
nPos = (int)(lp1 - (LPCTSTR)strSrc);
|
|
break;
|
|
}
|
|
|
|
++lp1;
|
|
}
|
|
|
|
return nPos;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
ReplaceStringInString(
|
|
OUT IN CString & strBuffer,
|
|
IN CString & strTarget,
|
|
IN CString & strReplacement,
|
|
IN BOOL fCaseSensitive
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Replace the first occurrence of a string with a second string
|
|
inside a third string.
|
|
|
|
Arguments:
|
|
|
|
CString & strBuffer : Buffer in which to replace
|
|
CString & strTarget : String to look for
|
|
CString & strReplacement : String to replace it with
|
|
BOOL fCaseSensitive : TRUE for case sensitive replacement.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS for successful replacement.
|
|
ERROR_INVALID_PARAMETER if any string is empty,
|
|
ERROR_FILE_NOT_FOUND if the target string doesn't exist, or
|
|
another win32 error code indicating failure.
|
|
|
|
--*/
|
|
{
|
|
if (strBuffer.IsEmpty() || strTarget.IsEmpty() || strReplacement.IsEmpty())
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
DWORD err = ERROR_FILE_NOT_FOUND;
|
|
int nPos = fCaseSensitive
|
|
? strBuffer.Find(strTarget)
|
|
: CStringFindNoCase(strBuffer, strTarget);
|
|
|
|
if (nPos >= 0)
|
|
{
|
|
try
|
|
{
|
|
CString str(strBuffer.Left(nPos));
|
|
|
|
str += strReplacement;
|
|
str += strBuffer.Mid(nPos + strTarget.GetLength());
|
|
strBuffer = str;
|
|
|
|
err = ERROR_SUCCESS;
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
e->Delete();
|
|
err = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
DeflateEnvironmentVariablePath(
|
|
IN LPCTSTR lpszEnvVar,
|
|
IN OUT CString & strTarget
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Take a path, and if the path represents a superset of the
|
|
path in the environment variable given, replace the relevant
|
|
portion of the path with the environment variable.
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lpszEnvVar : Environment variable
|
|
CString & strTarget : Path
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if the replacement was done succesfully,
|
|
ERROR_FILE_NOT_FOUND if the path represented by the environment
|
|
variable is not contained within the string, or an other win32
|
|
error for error conditions
|
|
|
|
--*/
|
|
{
|
|
CError err;
|
|
CString strEnv;
|
|
|
|
if (!::GetEnvironmentVariable(
|
|
lpszEnvVar,
|
|
strEnv.GetBuffer(_MAX_PATH),
|
|
_MAX_PATH
|
|
))
|
|
{
|
|
err.SetLastWinError();
|
|
}
|
|
|
|
strEnv.ReleaseBuffer();
|
|
|
|
if (err.Succeeded())
|
|
{
|
|
try
|
|
{
|
|
CString strReplacement(_T("%"));
|
|
strReplacement += lpszEnvVar;
|
|
strReplacement += _T("%");
|
|
|
|
err = ReplaceStringInString(
|
|
strTarget,
|
|
strEnv,
|
|
strReplacement,
|
|
FALSE
|
|
);
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
err = ERROR_NOT_ENOUGH_MEMORY;
|
|
e->Delete();
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
LPCTSTR
|
|
GUIDToCString(
|
|
IN REFGUID guid,
|
|
OUT CString & str
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert a GUID to a CString, returning pointer to the string
|
|
|
|
Arguments:
|
|
|
|
REFGUID : GUID to be converted
|
|
CString & str : Output string buffer
|
|
|
|
Return Value:
|
|
|
|
Pointer to the string
|
|
|
|
--*/
|
|
{
|
|
LPTSTR lpGUID = str.GetBuffer(MAX_PATH);
|
|
|
|
if (lpGUID)
|
|
{
|
|
::StringFromGUID2(guid, lpGUID, MAX_PATH);
|
|
str.ReleaseBuffer();
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
CountCharsToDoubleNull(
|
|
IN LPCTSTR lp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Count TCHARS up to and including the double NULL.
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lp : TCHAR Stream
|
|
|
|
Return Value:
|
|
|
|
Number of chars up to and including the double NULL
|
|
|
|
--*/
|
|
{
|
|
int cChars = 0;
|
|
|
|
for(;;)
|
|
{
|
|
++cChars;
|
|
|
|
if (lp[0] == _T('\0') && lp[1] == _T('\0'))
|
|
{
|
|
return ++cChars;
|
|
}
|
|
|
|
++lp;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
ConvertDoubleNullListToStringList(
|
|
IN LPCTSTR lpstrSrc,
|
|
OUT CStringList & strlDest,
|
|
IN int cChars OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert a double null terminate list of null terminated strings to a more
|
|
manageable CStringListEx
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lpstrSrc : Source list of strings
|
|
CStringList & strlDest : Destination string list.
|
|
int cChars : Number of characters in double NULL list. if
|
|
-1, autodetermine length
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if the list was converted properly
|
|
ERROR_INVALID_PARAMETER if the list was empty
|
|
ERROR_NOT_ENOUGH_MEMORY if there was a mem exception
|
|
|
|
--*/
|
|
{
|
|
DWORD err = ERROR_SUCCESS;
|
|
|
|
if (lpstrSrc == NULL)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (cChars < 0)
|
|
{
|
|
//
|
|
// Calculate our own size. This might be off if multiple
|
|
// blank linkes (0) appear in the multi_sz, so the character
|
|
// size is definitely preferable
|
|
//
|
|
cChars = CountCharsToDoubleNull(lpstrSrc);
|
|
}
|
|
|
|
try
|
|
{
|
|
strlDest.RemoveAll();
|
|
|
|
if (cChars == 2 && *lpstrSrc == _T('\0'))
|
|
{
|
|
//
|
|
// Special case: MULTI_SZ containing only
|
|
// a double NULL are in fact blank entirely.
|
|
//
|
|
// N.B. IMHO this is a metabase bug -- RonaldM
|
|
//
|
|
--cChars;
|
|
}
|
|
|
|
//
|
|
// Grab strings until only the final NULL remains
|
|
//
|
|
while (cChars > 1)
|
|
{
|
|
CString strTmp = lpstrSrc;
|
|
strlDest.AddTail(strTmp);
|
|
lpstrSrc += (strTmp.GetLength() + 1);
|
|
cChars -= (strTmp.GetLength() + 1);
|
|
}
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
TRACEEOLID("!!! exception building stringlist");
|
|
err = ERROR_NOT_ENOUGH_MEMORY;
|
|
e->Delete();
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
ConvertStringListToDoubleNullList(
|
|
IN CStringList & strlSrc,
|
|
OUT DWORD & cchDest,
|
|
OUT LPTSTR & lpstrDest
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Flatten the string list into a double null terminated list
|
|
of null terminated strings.
|
|
|
|
Arguments:
|
|
|
|
CStringList & strlSrc : Source string list
|
|
DWORD & cchDest : Size in characters of the resultant array
|
|
(including terminating NULLs)
|
|
LPTSTR & lpstrDest : Allocated flat array.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if the list was converted properly
|
|
ERROR_INVALID_PARAMETER if the list was empty
|
|
ERROR_NOT_ENOUGH_MEMORY if there was a mem exception
|
|
|
|
--*/
|
|
{
|
|
cchDest = 0;
|
|
lpstrDest = NULL;
|
|
BOOL fNullPad = FALSE;
|
|
|
|
//
|
|
// Compute total size in characters
|
|
//
|
|
POSITION pos;
|
|
|
|
for(pos = strlSrc.GetHeadPosition(); pos != NULL; /**/ )
|
|
{
|
|
CString & str = strlSrc.GetNext(pos);
|
|
|
|
TRACEEOLID(str);
|
|
|
|
cchDest += str.GetLength() + 1;
|
|
}
|
|
|
|
if (!cchDest)
|
|
{
|
|
//
|
|
// Special case: A totally empty MULTI_SZ
|
|
// in fact consists of 2 (final) NULLS, instead
|
|
// of 1 (final) NULL. This is required by the
|
|
// metabase, but should be a bug. See note
|
|
// at reversal function above.
|
|
//
|
|
++cchDest;
|
|
fNullPad = TRUE;
|
|
}
|
|
|
|
//
|
|
// Remember final NULL
|
|
//
|
|
cchDest += 1;
|
|
|
|
lpstrDest = AllocTString(cchDest);
|
|
|
|
if (lpstrDest == NULL)
|
|
{
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
LPTSTR pch = lpstrDest;
|
|
|
|
for(pos = strlSrc.GetHeadPosition(); pos != NULL; /**/ )
|
|
{
|
|
CString & str = strlSrc.GetNext(pos);
|
|
|
|
lstrcpy(pch, (LPCTSTR)str);
|
|
pch += str.GetLength();
|
|
*pch++ = _T('\0');
|
|
}
|
|
|
|
*pch++ = _T('\0');
|
|
|
|
if (fNullPad)
|
|
{
|
|
*pch++ = _T('\0');
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
ConvertSepLineToStringList(
|
|
IN LPCTSTR lpstrIn,
|
|
OUT CStringList & strlOut,
|
|
IN LPCTSTR lpstrSep
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert a line containing multiple strings separated by
|
|
a given character to a CStringListEx
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lpstrIn : Input line
|
|
CStringListEx & strlOut : Output stringlist
|
|
LPCTSTR lpstrSep : List of separators
|
|
|
|
Return Value:
|
|
|
|
The number of items added
|
|
|
|
--*/
|
|
{
|
|
int cItems = 0;
|
|
strlOut.RemoveAll();
|
|
|
|
try
|
|
{
|
|
CString strSrc(lpstrIn);
|
|
LPTSTR lp = strSrc.GetBuffer(0);
|
|
lp = _tcstok(lp, lpstrSep);
|
|
|
|
while (lp)
|
|
{
|
|
CString str(lp);
|
|
|
|
strlOut.AddTail(str);
|
|
lp = _tcstok(NULL, lpstrSep);
|
|
++cItems;
|
|
}
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
TRACEEOLID("Exception converting CSV list to stringlist");
|
|
e->ReportError();
|
|
e->Delete();
|
|
}
|
|
|
|
return cItems;
|
|
}
|
|
|
|
|
|
|
|
|
|
LPCTSTR
|
|
ConvertStringListToSepLine(
|
|
IN CStringList & strlIn,
|
|
OUT CString & strOut,
|
|
IN LPCTSTR lpstrSep
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert stringlist into a single CString, each entry separated by the given
|
|
separator string.
|
|
|
|
Arguments:
|
|
|
|
CStringListEx & strlIn : Input stringlist
|
|
CString & strOut : Output string
|
|
LPCTSTR lpstrSep : Separator string
|
|
|
|
Return Value:
|
|
|
|
Pointer to the output string.
|
|
|
|
--*/
|
|
{
|
|
strOut.Empty();
|
|
POSITION pos = strlIn.GetHeadPosition();
|
|
|
|
while(pos)
|
|
{
|
|
CString & str = strlIn.GetNext(pos);
|
|
|
|
strOut += str;
|
|
strOut += lpstrSep;
|
|
}
|
|
|
|
//
|
|
// Fix for bug #286824
|
|
// Remove separator from the last line: it looks ugly in edit control
|
|
//
|
|
if (!strOut.IsEmpty())
|
|
{
|
|
strOut.GetBufferSetLength(strOut.GetLength() - 1);
|
|
strOut.ReleaseBuffer();
|
|
}
|
|
|
|
return strOut;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
CStringListEx::operator ==(
|
|
IN const CStringList & strl
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Compare against CStringList. In order for two CStringLists to match,
|
|
they must match in every element, which must be in the same order.
|
|
|
|
Arguments:
|
|
|
|
CStringList strl : String list to compare against.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the two string lists are identical
|
|
|
|
--*/
|
|
{
|
|
if (strl.GetCount() != GetCount())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
POSITION posa = strl.GetHeadPosition();
|
|
POSITION posb = GetHeadPosition();
|
|
|
|
while (posa)
|
|
{
|
|
ASSERT(posa);
|
|
ASSERT(posb);
|
|
|
|
CString & strA = strl.GetNext(posa);
|
|
CString & strB = GetNext(posb);
|
|
|
|
if (strA != strB)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
CStringListEx &
|
|
CStringListEx::operator =(
|
|
IN const CStringList & strl
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Assignment operator
|
|
|
|
Arguments:
|
|
|
|
const CStringList & strl : Source stringlist
|
|
|
|
Return Value:
|
|
|
|
Reference to this
|
|
|
|
--*/
|
|
{
|
|
RemoveAll();
|
|
POSITION pos = strl.GetHeadPosition();
|
|
|
|
while(pos)
|
|
{
|
|
CString & str = strl.GetNext(pos);
|
|
AddTail(str);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
CvtStringToLong(
|
|
IN LPCTSTR lpNumber,
|
|
OUT DWORD * pdwValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Helper function to convert string (hex or decimal) to a dword.
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lpNumber : Input number
|
|
DWORD * pdwValue : Returns the value
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure
|
|
|
|
--*/
|
|
{
|
|
const TCHAR rgchHex[] = _T("00112233445566778899aAbBcCdDeEfF");
|
|
|
|
DWORD dwResult = 0L;
|
|
DWORD dwResultPrev = 0L;
|
|
|
|
//
|
|
// Assume a decimal base
|
|
//
|
|
DWORD dwBase = 10L;
|
|
|
|
ASSERT_READ_PTR(lpNumber);
|
|
ASSERT_WRITE_PTR(pdwValue);
|
|
|
|
while (*lpNumber == _T(' ') || *lpNumber == _T('0'))
|
|
{
|
|
++lpNumber;
|
|
}
|
|
|
|
if (*lpNumber == _T('x') || *lpNumber == _T('X'))
|
|
{
|
|
dwBase = 16L;
|
|
++lpNumber;
|
|
}
|
|
|
|
while (*lpNumber)
|
|
{
|
|
DWORD dwDigit;
|
|
|
|
//
|
|
// Search the character in the hexadecimal string
|
|
//
|
|
LPCTSTR pchDigit = _tcschr(rgchHex, *lpNumber);
|
|
|
|
if (!pchDigit)
|
|
{
|
|
//
|
|
// Character is not found
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
dwDigit = DIFF(pchDigit - rgchHex) >> 1;
|
|
|
|
if (dwDigit >= dwBase)
|
|
{
|
|
//
|
|
// Hexadecimal character in a decimal integer
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
dwResultPrev = dwResult;
|
|
dwResult *= dwBase;
|
|
dwResult += dwDigit;
|
|
|
|
if (dwResult < dwResultPrev)
|
|
{
|
|
//
|
|
// Overflow
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Parse the next character
|
|
//
|
|
++lpNumber;
|
|
}
|
|
|
|
*pdwValue = dwResult;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
const LPCTSTR g_cszMonths[] =
|
|
{
|
|
_T("Jan"),
|
|
_T("Feb"),
|
|
_T("Mar"),
|
|
_T("Apr"),
|
|
_T("May"),
|
|
_T("Jun"),
|
|
_T("Jul"),
|
|
_T("Aug"),
|
|
_T("Sep"),
|
|
_T("Oct"),
|
|
_T("Nov"),
|
|
_T("Dec"),
|
|
};
|
|
|
|
|
|
|
|
const LPCTSTR g_cszWeekDays[] =
|
|
{
|
|
_T("Sun"),
|
|
_T("Mon"),
|
|
_T("Tue"),
|
|
_T("Wed"),
|
|
_T("Thu"),
|
|
_T("Fri"),
|
|
_T("Sat"),
|
|
};
|
|
|
|
|
|
|
|
inline BOOL SkipTillDigit(LPCTSTR & lp)
|
|
{
|
|
while (lp && *lp && !_istdigit(*lp)) ++lp;
|
|
|
|
return lp != NULL;
|
|
}
|
|
|
|
|
|
|
|
inline BOOL SkipPastDigits(LPCTSTR & lp)
|
|
{
|
|
while (lp && *lp && _istdigit(*lp)) ++lp;
|
|
|
|
return lp != NULL;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
FetchIntField(
|
|
LPCTSTR & lp,
|
|
int & n
|
|
)
|
|
{
|
|
if (SkipTillDigit(lp))
|
|
{
|
|
n = _ttoi(lp);
|
|
if (n < 0)
|
|
{
|
|
ASSERT_MSG("Bogus string->int");
|
|
return FALSE;
|
|
}
|
|
|
|
return SkipPastDigits(lp);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
MatchString(
|
|
LPCTSTR lpTarget,
|
|
const LPCTSTR * rglp,
|
|
int cElements,
|
|
int & idx
|
|
)
|
|
{
|
|
for (idx = 0; idx < cElements; ++idx)
|
|
{
|
|
if (!_tcsnicmp(lpTarget, rglp[idx], _tcslen(rglp[idx])))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static g_dwCurrentTimeZone = TIME_ZONE_ID_INVALID;
|
|
static TIME_ZONE_INFORMATION g_tzInfo;
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
CvtGMTStringToInternal(
|
|
IN LPCTSTR lpTime,
|
|
OUT time_t * ptValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert GMT string to time in the local timezone format
|
|
|
|
Arguments:
|
|
|
|
IN LPCSTSTR lpTime : Input time string
|
|
OUT time_t * ptValue : Output time_t
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure
|
|
|
|
--*/
|
|
{
|
|
ASSERT_READ_PTR(lpTime);
|
|
ASSERT_WRITE_PTR(ptValue);
|
|
|
|
int year, month, day, hour, minutes, seconds;
|
|
|
|
do
|
|
{
|
|
if (!lpTime || !ptValue)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!FetchIntField(lpTime, day))
|
|
{
|
|
break;
|
|
}
|
|
|
|
++lpTime; // Skip separator
|
|
|
|
if (!MatchString(lpTime, g_cszMonths, 12, month))
|
|
{
|
|
break;
|
|
}
|
|
|
|
++month; // 0-based.
|
|
|
|
if (!FetchIntField(lpTime, year) ||
|
|
!FetchIntField(lpTime, hour) ||
|
|
!FetchIntField(lpTime, minutes) ||
|
|
!FetchIntField(lpTime, seconds))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (year < 100)
|
|
{
|
|
year += ((year < 50) ? 2000 : 1900);
|
|
}
|
|
|
|
CTime time(year, month, day, hour, minutes, seconds);
|
|
if (time.GetTime() == (time_t)-1L)
|
|
{
|
|
break;
|
|
}
|
|
|
|
*ptValue = time.GetTime();
|
|
|
|
TRACEEOLID("GMT Time is " << ctime(ptValue));
|
|
|
|
//
|
|
// Adjust for local time zone
|
|
//
|
|
RaiseThreadProtection();
|
|
|
|
if (g_dwCurrentTimeZone == TIME_ZONE_ID_INVALID)
|
|
{
|
|
//
|
|
// GetTimeZoneInfo() is expensive, so cache its output
|
|
//
|
|
g_dwCurrentTimeZone = GetTimeZoneInformation(&g_tzInfo);
|
|
}
|
|
|
|
LowerThreadProtection();
|
|
|
|
if (g_dwCurrentTimeZone != TIME_ZONE_ID_INVALID)
|
|
{
|
|
_tzset();
|
|
|
|
struct tm * tb = localtime(ptValue);
|
|
|
|
BOOL fDST = tb->tm_isdst;
|
|
|
|
RaiseThreadProtection();
|
|
*ptValue -= (g_tzInfo.Bias * 60L);
|
|
|
|
TRACEEOLID("local time (w/o dst adjustment) is " << ctime(ptValue));
|
|
|
|
if (fDST)
|
|
{
|
|
*ptValue -= (g_tzInfo.DaylightBias * 60L);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Almost always this is zero
|
|
//
|
|
*ptValue -= (g_tzInfo.StandardBias * 60L);
|
|
}
|
|
LowerThreadProtection();
|
|
|
|
TRACEEOLID("Local time adjusted for dst is " << ctime(ptValue));
|
|
}
|
|
|
|
//
|
|
// Time OK
|
|
//
|
|
return TRUE;
|
|
}
|
|
while(FALSE);
|
|
|
|
//
|
|
// Set max abs timeout value.
|
|
//
|
|
CTime time(2037, 12, 31, 0, 0, 0);
|
|
*ptValue = (time_t)time.GetTime();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CvtInternalToGMTString(
|
|
IN time_t tm,
|
|
OUT CString & str
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reverse of the above function. Convert internal time_t to a GMT
|
|
time string (converting to GM time in the process)
|
|
|
|
Arguments:
|
|
|
|
time_t tm : Input time_t
|
|
CString & str : Out CString
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
try
|
|
{
|
|
_tzset();
|
|
|
|
struct tm * tb = gmtime(&tm);
|
|
|
|
str.Format(
|
|
_T("%-3.3s, %02d %-3.3s %4d %02d:%02d:%02d GMT"),
|
|
g_cszWeekDays[tb->tm_wday],
|
|
tb->tm_mday,
|
|
g_cszMonths[tb->tm_mon],
|
|
tb->tm_year + 1900,
|
|
tb->tm_hour,
|
|
tb->tm_min,
|
|
tb->tm_sec
|
|
);
|
|
|
|
TRACEEOLID("Time string is " << str);
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
e->ReportError();
|
|
e->Delete();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// International numeric strings
|
|
//
|
|
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
|
|
|
|
//
|
|
// Initialize library
|
|
//
|
|
BOOL
|
|
InitIntlSettings()
|
|
{
|
|
#ifdef _MT
|
|
INITIALIZE_CRITICAL_SECTION(&_csSect);
|
|
#endif // _MT
|
|
|
|
return CINumber::Allocate();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// De-initialize library
|
|
//
|
|
void
|
|
TerminateIntlSettings()
|
|
{
|
|
CINumber::DeAllocate();
|
|
|
|
#ifdef _MT
|
|
DeleteCriticalSection(&_csSect);
|
|
#endif // _MT
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Static Member Initialization
|
|
//
|
|
// NOTE: MFC classes CANNOT be global in an AFX extension, as they
|
|
// will not be initialized properly. Solution is to allocate them.
|
|
//
|
|
CString * CINumber::_pstrThousandSeparator = NULL;
|
|
CString * CINumber::_pstrDecimalPoint = NULL;
|
|
CString * CINumber::_pstrBadNumber = NULL;
|
|
CString * CINumber::_pstrCurrency = NULL;
|
|
CString * CINumber::_pstr = NULL;
|
|
BOOL CINumber::_fAllocated = FALSE;
|
|
BOOL CINumber::_fCurrencyPrefix = TRUE;
|
|
BOOL CINumber::_fInitialized = FALSE;
|
|
|
|
|
|
|
|
CINumber::CINumber()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
if (!CINumber::_fInitialized)
|
|
{
|
|
CINumber::Initialize();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
CINumber::~CINumber()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Destructor
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
BOOL
|
|
CINumber::Allocate()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocate with US settings
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE if allocation was successfull, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
RaiseThreadProtection();
|
|
|
|
ASSERT(!IsAllocated());
|
|
|
|
if (!IsAllocated())
|
|
{
|
|
try
|
|
{
|
|
//
|
|
// NOTE: Cannot use globals for these, because
|
|
// they will not be initialized properly
|
|
// when used in an extension dll.
|
|
//
|
|
CINumber::_pstrThousandSeparator = new CString(_T(","));
|
|
CINumber::_pstrDecimalPoint = new CString(_T("."));
|
|
CINumber::_pstrBadNumber = new CString(_T("--"));
|
|
CINumber::_pstrCurrency = new CString(_T("$ "));
|
|
CINumber::_pstr = new CString;
|
|
_fAllocated = TRUE;
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
TRACEEOLID("Initialization Failed");
|
|
e->ReportError();
|
|
e->Delete();
|
|
}
|
|
}
|
|
|
|
LowerThreadProtection();
|
|
|
|
return IsAllocated();
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
void
|
|
CINumber::DeAllocate()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Clean up allocations
|
|
|
|
Arguments:
|
|
|
|
N/A
|
|
|
|
Return Value:
|
|
|
|
N/A
|
|
|
|
--*/
|
|
{
|
|
RaiseThreadProtection();
|
|
|
|
ASSERT(IsAllocated());
|
|
|
|
if (IsAllocated())
|
|
{
|
|
MAKE_NULL(CINumber::_pstrThousandSeparator);
|
|
MAKE_NULL(CINumber::_pstrDecimalPoint);
|
|
MAKE_NULL(CINumber::_pstrBadNumber);
|
|
MAKE_NULL(CINumber::_pstrCurrency);
|
|
MAKE_NULL(CINumber::_pstr);
|
|
}
|
|
|
|
LowerThreadProtection();
|
|
|
|
_fAllocated = FALSE;
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
BOOL
|
|
CINumber::Initialize(
|
|
IN BOOL fUserSetting /* TRUE */
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize all the international settings, such as thousand
|
|
separators and decimal points
|
|
|
|
Parameters:
|
|
|
|
BOOL fUserSetting If TRUE, use current user settings,
|
|
if FALSE use system settings.
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure
|
|
|
|
Notes:
|
|
|
|
Note that this function only needs to be explicitly called
|
|
when the country settings have changed, or when system
|
|
settings are desired (user is default)
|
|
|
|
--*/
|
|
{
|
|
#define MAXLEN 6
|
|
|
|
int cErrors = 0;
|
|
|
|
TRACEEOLID("Getting locale-dependend information");
|
|
|
|
ASSERT(IsAllocated());
|
|
if (!IsAllocated())
|
|
{
|
|
Allocate();
|
|
}
|
|
|
|
RaiseThreadProtection();
|
|
|
|
try
|
|
{
|
|
LCID lcid = fUserSetting
|
|
? ::GetUserDefaultLCID()
|
|
: GetSystemDefaultLCID();
|
|
|
|
LCTYPE lctype = fUserSetting ? 0 : LOCALE_NOUSEROVERRIDE;
|
|
|
|
//
|
|
// Get Decimal Point
|
|
//
|
|
if (!::GetLocaleInfo(
|
|
lcid,
|
|
LOCALE_SDECIMAL | lctype,
|
|
CINumber::_pstrDecimalPoint->GetBuffer(MAXLEN),
|
|
MAXLEN
|
|
))
|
|
{
|
|
TRACEEOLID("Unable to get intl decimal point");
|
|
++cErrors;
|
|
}
|
|
|
|
CINumber::_pstrDecimalPoint->ReleaseBuffer();
|
|
|
|
//
|
|
// Get Thousand Separator
|
|
//
|
|
if (!::GetLocaleInfo(
|
|
lcid, LOCALE_STHOUSAND | lctype,
|
|
CINumber::_pstrThousandSeparator->GetBuffer(MAXLEN),
|
|
MAXLEN
|
|
))
|
|
{
|
|
TRACEEOLID("Unable to get 1000 separator");
|
|
++cErrors;
|
|
}
|
|
|
|
CINumber::_pstrThousandSeparator->ReleaseBuffer();
|
|
|
|
#ifndef _UNICODE
|
|
//
|
|
// Some countries have a space as a 1000 separator,
|
|
// but for some reason, this is ansi 160, which
|
|
// shows up as a space fine on windows apps,
|
|
// looks like garbage on console apps.
|
|
//
|
|
if ((*CINumber::_pstrThousandSeparator)[0] == CHAR(160))
|
|
{
|
|
CINumber::_pstrThousandSeparator->SetAt(0, ' ');
|
|
TRACEEOLID("Space 1000 separator substituted");
|
|
}
|
|
#endif // _UNICODE
|
|
|
|
//
|
|
// Get currency symbol
|
|
//
|
|
if (!::GetLocaleInfo(
|
|
lcid,
|
|
LOCALE_SCURRENCY | lctype,
|
|
CINumber::_pstrCurrency->GetBuffer(MAXLEN),
|
|
MAXLEN
|
|
))
|
|
{
|
|
TRACEEOLID("Unable to get currency symbol");
|
|
++cErrors;
|
|
}
|
|
|
|
CINumber::_pstrCurrency->ReleaseBuffer();
|
|
}
|
|
|
|
catch(CMemoryException * e)
|
|
{
|
|
TRACEEOLID("!!!exception in getting intl settings:");
|
|
e->ReportError();
|
|
e->Delete();
|
|
++cErrors;
|
|
}
|
|
|
|
TRACEEOLID("Thousand Separator . . . . . : " << *CINumber::_pstrThousandSeparator);
|
|
TRACEEOLID("Decimal Point . . . . . . . : " << *CINumber::_pstrDecimalPoint);
|
|
TRACEEOLID("Currency Symbol. . . . . . . : " << *CINumber::_pstrCurrency);
|
|
TRACEEOLID("Bad number . . . . . . . . . : " << *CINumber::_pstrBadNumber);
|
|
TRACEEOLID("Currency Prefix. . . . . . . : " << CINumber::_fCurrencyPrefix);
|
|
|
|
CINumber::_fInitialized = TRUE;
|
|
|
|
LowerThreadProtection();
|
|
|
|
return cErrors == 0;
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
double
|
|
CINumber::BuildFloat(
|
|
IN const LONG lInteger,
|
|
IN const LONG lFraction
|
|
)
|
|
/*++
|
|
|
|
Return Value:
|
|
|
|
Combine integer and fraction to form float
|
|
|
|
Parameters:
|
|
|
|
const LONG lInteger Integer portion
|
|
const LONG lFraction Fractional portion
|
|
|
|
Return Value:
|
|
|
|
float value
|
|
|
|
--*/
|
|
{
|
|
double flValue = 0.0;
|
|
|
|
//
|
|
// Negative fractions?
|
|
//
|
|
ASSERT(lFraction >= 0);
|
|
|
|
if (lFraction >= 0)
|
|
{
|
|
flValue = (double)lFraction;
|
|
|
|
while (flValue >= 1.0)
|
|
{
|
|
flValue /= 10.0;
|
|
}
|
|
|
|
//
|
|
// Re-add (or subtract if the original number
|
|
// was negative) the fractional part
|
|
//
|
|
if (lInteger > 0L)
|
|
{
|
|
flValue += (double)lInteger;
|
|
}
|
|
else
|
|
{
|
|
flValue -= (double)lInteger;
|
|
flValue = -flValue;
|
|
}
|
|
}
|
|
|
|
return flValue;
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
LPCTSTR
|
|
CINumber::ConvertLongToString(
|
|
IN const LONG lSrc,
|
|
OUT CString & str
|
|
)
|
|
/*++
|
|
|
|
CINumber::ConvertLongToString
|
|
|
|
Purpose:
|
|
|
|
Convert long number to string with 1000 separators
|
|
|
|
Parameters:
|
|
|
|
const LONG lSrc Source number
|
|
CString & str String to write to
|
|
|
|
Return Value:
|
|
|
|
Pointer to converted string
|
|
|
|
--*/
|
|
{
|
|
LPTSTR lpOutString = str.GetBuffer(16);
|
|
|
|
//
|
|
// Forget about the negative sign for now.
|
|
//
|
|
LONG lNum = (lSrc >= 0L) ? lSrc : -lSrc;
|
|
int outstrlen = 0;
|
|
|
|
do
|
|
{
|
|
lpOutString[outstrlen++] = _T('0') + (TCHAR)(lNum % 10L);
|
|
lNum /= 10L;
|
|
|
|
//
|
|
// if more digits left and we're on a 1000 boundary (printed 3 digits,
|
|
// or 3 digits + n*(3 digits + 1 comma), then print a 1000 separator.
|
|
// Note: will only work if the 1000 separator is 1 character.
|
|
//
|
|
ASSERT(CINumber::_pstrThousandSeparator->GetLength() == 1);
|
|
|
|
if (lNum != 0L && (outstrlen == 3 || outstrlen == 7 || outstrlen == 11))
|
|
{
|
|
lstrcpy(lpOutString + outstrlen, *CINumber::_pstrThousandSeparator);
|
|
outstrlen += CINumber::_pstrThousandSeparator->GetLength();
|
|
}
|
|
|
|
}
|
|
while (lNum > 0L);
|
|
|
|
//
|
|
// Add a negative sign if necessary.
|
|
//
|
|
if (lSrc < 0L)
|
|
{
|
|
lpOutString[outstrlen++] = _T('-');
|
|
}
|
|
|
|
str.ReleaseBuffer(outstrlen);
|
|
str.MakeReverse();
|
|
|
|
return (LPCTSTR)str;
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
LPCTSTR
|
|
CINumber::ConvertFloatToString(
|
|
IN const double flSrc,
|
|
IN int nPrecision,
|
|
OUT CString & str
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert floating point number to string represenation
|
|
|
|
Parameters:
|
|
|
|
const double flSrc Source floating point number
|
|
int nPrecision Number of decimal points
|
|
CString & str String to convert to
|
|
|
|
Return Value:
|
|
|
|
Pointer to converted string.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Forget about the negative sign for now,
|
|
// and the fractional portion.
|
|
//
|
|
TCHAR szFraction[256];
|
|
LPCTSTR lpFraction = NULL;
|
|
|
|
::_stprintf(szFraction, _T("%.*f"), nPrecision, flSrc);
|
|
lpFraction = ::_tcschr(szFraction, _T('.') );
|
|
ASSERT(lpFraction != NULL);
|
|
++lpFraction;
|
|
|
|
CINumber::ConvertLongToString((LONG)flSrc, str);
|
|
|
|
str += *CINumber::_pstrDecimalPoint + lpFraction;
|
|
|
|
return (LPCTSTR)str;
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
BOOL
|
|
CINumber::ConvertStringToLong(
|
|
IN LPCTSTR lpsrc,
|
|
OUT LONG & lValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert string to long integer. 1000 Separators will be treated
|
|
correctly.
|
|
|
|
Parameters:
|
|
|
|
LPCTSTR lpsrc Source string
|
|
LONG & lValue Value to convert to. Will be 0 in case of error
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure.
|
|
|
|
--*/
|
|
{
|
|
CString strNumber(lpsrc);
|
|
LONG lBase = 1L;
|
|
BOOL fNegative = FALSE;
|
|
|
|
lValue = 0L;
|
|
|
|
//
|
|
// Empty strings are invalid
|
|
//
|
|
if (strNumber.IsEmpty())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check for negative sign (at the end only)
|
|
//
|
|
if (strNumber[0] == _T('-'))
|
|
{
|
|
fNegative = TRUE;
|
|
}
|
|
|
|
strNumber.MakeReverse();
|
|
|
|
//
|
|
// Strip negative sign
|
|
//
|
|
if (fNegative)
|
|
{
|
|
strNumber.ReleaseBuffer(strNumber.GetLength()-1);
|
|
}
|
|
|
|
//
|
|
// Make sure the 1000 separator is only 1 char. See note below
|
|
//
|
|
ASSERT(CINumber::_pstrThousandSeparator->GetLength() == 1);
|
|
|
|
for (int i = 0; i < strNumber.GetLength(); ++i)
|
|
{
|
|
if ((strNumber[i] >= _T('0')) && (strNumber[i] <= _T('9')))
|
|
{
|
|
LONG lDigit = (LONG)(strNumber[i] - _T('0'));
|
|
if (lDigit != 0L)
|
|
{
|
|
LONG lOldValue = lValue;
|
|
LONG lDelta = (lDigit * lBase);
|
|
if (lDelta / lDigit != lBase)
|
|
{
|
|
TRACEEOLID("Overflow!");
|
|
lValue = 0x7fffffff;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
lValue += lDelta;
|
|
|
|
if (lValue - lDelta != lOldValue)
|
|
{
|
|
TRACEEOLID("Overflow!");
|
|
lValue = 0x7fffffff;
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
lBase *= 10L;
|
|
}
|
|
//
|
|
// It's not a digit, maybe a thousand separator?
|
|
// CAVEAT: If a thousand separator of more than
|
|
// one character is used, this won't work.
|
|
//
|
|
else if ((strNumber[i] != (*CINumber::_pstrThousandSeparator)[0])
|
|
|| (i != 3) && (i != 7) && (i != 11))
|
|
{
|
|
//
|
|
// This is just invalid, since it is not a thousand
|
|
// separator in the proper location, nor a negative
|
|
// sign.
|
|
//
|
|
TRACEEOLID("Invalid character " << (BYTE)strNumber[i] << " encountered");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (fNegative)
|
|
{
|
|
lValue = -lValue;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
BOOL
|
|
CINumber::ConvertStringToFloat(
|
|
IN LPCTSTR lpsrc,
|
|
OUT double & flValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert fully decorated floating point string to double
|
|
|
|
Parameters:
|
|
|
|
LPCTSTR lpsrc Source string
|
|
double & flValue float value generated from string
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE for failure
|
|
|
|
--*/
|
|
{
|
|
CString strNumber(lpsrc);
|
|
|
|
//
|
|
// This only works if the decimal point is a
|
|
// single character
|
|
//
|
|
ASSERT(CINumber::_pstrDecimalPoint->GetLength() == 1);
|
|
|
|
//
|
|
// Strip off the > 0 part
|
|
//
|
|
LONG lFraction = 0;
|
|
|
|
int nPoint = strNumber.ReverseFind((*CINumber::_pstrDecimalPoint)[0]);
|
|
|
|
if (nPoint >= 0)
|
|
{
|
|
//
|
|
// Convert fractional part
|
|
//
|
|
LPCTSTR lpszFraction = (LPCTSTR)strNumber + ++nPoint;
|
|
lFraction = ::_ttol(lpszFraction);
|
|
strNumber.ReleaseBuffer(--nPoint);
|
|
}
|
|
|
|
//
|
|
// Convert integer part
|
|
//
|
|
LONG lInteger;
|
|
|
|
if (ConvertStringToLong(strNumber, lInteger))
|
|
{
|
|
flValue = CINumber::BuildFloat(lInteger, lFraction);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
CILong::CILong()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor without arguments
|
|
|
|
Parameters:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
N/A
|
|
|
|
--*/
|
|
: m_lValue(0L)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
CILong::CILong(
|
|
IN LONG lValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor taking LONG argument
|
|
|
|
Parameters:
|
|
|
|
LONG lValue Value to be set
|
|
|
|
Return Value:
|
|
|
|
N/A
|
|
|
|
--*/
|
|
: m_lValue(lValue)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
CILong::CILong(
|
|
IN LPCTSTR lpszValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor taking string argument
|
|
|
|
Parameters:
|
|
|
|
LPCTSTR lpszValue String number
|
|
|
|
Return Value:
|
|
|
|
N/A
|
|
|
|
--*/
|
|
{
|
|
ConvertStringToLong(lpszValue, m_lValue);
|
|
}
|
|
|
|
|
|
|
|
CILong &
|
|
CILong::operator =(
|
|
IN LONG lValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Assignment operator taking long value
|
|
|
|
Parameters:
|
|
|
|
LONG lValue Value to be set
|
|
|
|
Return Value:
|
|
|
|
this object
|
|
|
|
--*/
|
|
{
|
|
m_lValue = lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
CILong &
|
|
CILong::operator =(
|
|
IN LPCTSTR lpszValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Assignment operator taking string value
|
|
|
|
Parameters:
|
|
|
|
LPCTSTR lpszValue String number
|
|
|
|
Return Value:
|
|
|
|
this object
|
|
|
|
--*/
|
|
{
|
|
ConvertStringToLong(lpszValue, m_lValue);
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Arithmetic Shorthand operators
|
|
//
|
|
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
|
|
|
|
CILong &
|
|
CILong::operator +=(
|
|
IN const LONG lValue
|
|
)
|
|
{
|
|
m_lValue += lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator +=(
|
|
IN const LPCTSTR lpszValue
|
|
)
|
|
{
|
|
LONG lValue;
|
|
|
|
ConvertStringToLong(lpszValue, lValue);
|
|
|
|
m_lValue += lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator +=(
|
|
IN const CILong& value
|
|
)
|
|
{
|
|
m_lValue += value.m_lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator -=(
|
|
IN const LONG lValue
|
|
)
|
|
{
|
|
m_lValue -= lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator -=(
|
|
IN const LPCTSTR lpszValue
|
|
)
|
|
{
|
|
LONG lValue;
|
|
|
|
ConvertStringToLong(lpszValue, lValue);
|
|
|
|
m_lValue -= lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator -=(
|
|
IN const CILong & value
|
|
)
|
|
{
|
|
m_lValue -= value.m_lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator *=(
|
|
IN const LONG lValue
|
|
)
|
|
{
|
|
m_lValue *= lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator *=(
|
|
IN const LPCTSTR lpszValue
|
|
)
|
|
{
|
|
LONG lValue;
|
|
|
|
ConvertStringToLong(lpszValue, lValue);
|
|
|
|
m_lValue *= lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator *=(
|
|
IN const CILong & value
|
|
)
|
|
{
|
|
m_lValue *= value.m_lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator /=(
|
|
IN const LONG lValue
|
|
)
|
|
{
|
|
m_lValue /= lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator /=(
|
|
IN const LPCTSTR lpszValue
|
|
)
|
|
{
|
|
LONG lValue;
|
|
|
|
ConvertStringToLong(lpszValue, lValue);
|
|
if (lValue != 0)
|
|
{
|
|
m_lValue /= lValue;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// As above
|
|
//
|
|
CILong &
|
|
CILong::operator /=(
|
|
IN const CILong& value
|
|
)
|
|
{
|
|
m_lValue /= value.m_lValue;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|