/*++

   Copyright    (c)    1994-1998    Microsoft Corporation

   Module  Name :

        strfrn.cpp

   Abstract:

        String Functions

   Author:

        Ronald Meijer (ronaldm)
        Munged to work with setup by BoydM

   Project:

        Internet Services Manager
        And now setup too

   Revision History:

--*/

//
// Include Files
//
#include "stdafx.h"
#include "strfn.h"
#include <pudebug.h>


#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


LPWSTR
AllocWideString(
    IN LPCTSTR lpString
    )
/*++

Routine Description:

    Convert the incoming string to an wide 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+1) * sizeof(WCHAR);
    LPWSTR lp = (LPWSTR)AllocMem(nLength);
    if (lp)
    {
#ifdef UNICODE
        lstrcpy(lp, lpString);
#else
        ::MultiByteToWideChar(
            CP_ACP, // code page  
            MB_PRECOMPOSED, // character-type options  
            lpString, // address of string to map  
            cChars, // number of characters in string  
            lp, // address of wide-character buffer  
            cChars+1  // size of buffer  
            ); 
#endif
    }

    return lp;
}

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)
    {
#ifdef UNICODE
        ::WideCharToMultiByte(
            CP_ACP,
            0,
            lpString,
            cChars + 1,
            lp,
            nLength,
            NULL,
            NULL
            );
#else
    lstrcpy(lp, lpString);
#endif
    }

    return lp;
}


LPTSTR
AllocString(
    IN LPCTSTR lpString
    )
/*++

Routine Description:

    Allocate and copy string

Arguments:

    LPCTSTR lpString        : Input string

Return Value:

    Pointer to the allocated string

--*/
{
    int nLength = lstrlen(lpString) + 1;
    LPTSTR lp = (LPTSTR)AllocMem(nLength * 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

--*/
{
    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('\\')     //
         && _tcschr(lp + 2, _T('\\')) // And have at least one more \ after that
           )
        {
            //
            // Yes, it's a UNC path
            //
            return TRUE;
        }
    }

    //
    // No, it's not
    //
    return FALSE;
}



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('\\');
}



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;
}


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;
    }
}

int
CountWCharsToDoubleNull(
    IN PWCHAR 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] == L'\0' && lp[1] == L'\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
ConvertWDoubleNullListToStringList(
    IN  PWCHAR 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 = CountWCharsToDoubleNull(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
ConvertStringListToWDoubleNullList(
    IN  CStringList & strlSrc,
    OUT DWORD & cchDest,
    OUT LPWSTR & lpstrDest
    )
/*++

Routine Description:

    Flatten the string list into a WIDE 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 = AllocWString(cchDest);
    if (lpstrDest == NULL)
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    LPWSTR pch = lpstrDest;
    LPWSTR pwstr;

    for(pos = strlSrc.GetHeadPosition(); pos != NULL; /**/ )
    {
        CString & str = strlSrc.GetNext(pos);


        // if we are not already UNICODE, we need to convert
#ifndef UNICODE
        pwstr = AllocWideString( (LPCTSTR)str );
#else
        pwstr = (LPWSTR)(LPCTSTR)str;
#endif
        if (pwstr == NULL)
        {
            return ERROR_NOT_ENOUGH_MEMORY;
        }

        wcscpy(pch, pwstr);
        pch += str.GetLength();
        *pch++ = L'\0';

#ifndef UNICODE
        // clean up the temporary wide string
        FreeMem( pwstr );
#endif
    }

    *pch++ = L'\0';

    if (fNullPad)
    {
        *pch++ = L'\0';
    }

    return ERROR_SUCCESS;
}


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 = StringTok(lp, lpstrSep);

        while (lp)
        {
            CString str(lp);

            strlOut.AddTail(str);
            lp = StringTok(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 seperated by the given
    seperator string.

Arguments:

    CStringListEx & strlIn  : Input stringlist
    CString & strOut        : Output string
    LPCTSTR lpstrSep        : Seperator string

Return Value:

    Pointer to the output string.

--*/
{
    __try 
    {
        strOut.Empty();
        POSITION pos = strlIn.GetHeadPosition();
        BOOL      fAddSep = FALSE;

        while(pos)
        {
            CString & str = strlIn.GetNext(pos);

            if ( fAddSep )
            {
                strOut += lpstrSep;
            }

            if (str)
            {
                strOut += str;
            }
            fAddSep = TRUE;
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        iisDebugOut((LOG_TYPE_WARN, _T("nException Caught in ConvertStringListToSepLine()=0x%x.."),GetExceptionCode()));
    }

    return strOut;
}



LPTSTR
StringTok(
    IN LPTSTR string,
    IN LPCTSTR control
    )
/*++

Routine Description:

    strtok replacement function.

Arguments:

    LPTSTR string       : string, see strtok
    LPCTSTR control     : seperators, see strtok

Return Value:

    Pointer to string or NULL, see strtok.

Notes:

    This function is NOT thread-safe.

--*/
{
    LPTSTR str;
    LPCTSTR ctrl = control;

    TCHAR map[32];

    static LPTSTR nextoken;

    //
    // Clear control map
    //
    ZeroMemory(map, sizeof(map));

    //
    // Set bits in delimiter table
    //
    do
    {
        map[*ctrl >> 3] |= (1 << (*ctrl & 7));
    }
    while (*ctrl++);

    //
    // Initialize str. If string is NULL, set str to the saved
    // pointer (i.e., continue breaking tokens out of the string
    // from the last StringTok call)
    //
    if (string != NULL)
    {
        str = string;
    }
    else
    {
        str = nextoken;
    }

    //
    // Find beginning of token (skip over leading delimiters). Note that
    // there is no token iff this loop sets str to point to the terminal
    // null (*str == '\0').
    //
#ifdef UNICODE
    //
    // To avoid index overflow, check non-ASCII characters (UNICODE)
    //
    while (!(*str & 0xff00) &&
        (map[*str >> 3] & (1 << (*str & 7))) && *str)
#else
    while ((map[*str >> 3] & (1 << (*str & 7))) && *str)
#endif // UNICODE
    {
        ++str;
    }

    string = str;

    //
    // Find the end of the token. If it is not the end of the string,
    // put a null there.
    //
    for ( /**/ ; *str ; str++ )
    {
#ifdef UNICODE
        //
        // To avoid index overflow, check non-ASCII characters (UNICODE)
        //
        if ( !(*str & 0xff00) &&
            map[*str >> 3] & (1 << (*str & 7)) )
#else
        //
        // Skip DBCS character (ANSI)
        //
        if (IsDBCSLeadByte(*str) && *(str + 1))
        {
            ++str;
        }
        else if ( map[*str >> 3] & (1 << (*str & 7)) )
#endif // UNICODE
        {
            *str++ = '\0';
            break;
        }
    }

    //
    // Update nextoken structure
    //
    nextoken = str;

    //
    // Determine if a token has been found.
    //
    return string != str ? string : NULL;
}



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;
}


/*
void
CopyCList(
    OUT CStringList & strlDest,
    IN  CStringList & strlSrc
    )
/*++

Routine Description:

    Assign one stringlist to another.  This is a simple member by member
    copy.

Arguments:

    CStringList & strlDest      : Destination stringlist
    CStringList & strlSrc       : Source stringlist

Return Value:

    None

--/
{
    strlDest.RemoveAll();
    POSITION pos = strlSrc.GetHeadPosition();
    while(pos)
    {
        CString & str = strlSrc.GetNext(pos);
        strlDest.AddTail(str);
    }
}


*/

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
SplitUserNameAndDomain(
    IN OUT CString & strUserName,
    IN CString & strDomainName
    )
/*++

Routine Description:

    Split the user name and domain from the given
    username, which is in the format "domain\user".

    Return TRUE if the user name contained a domain
    FALSE if it did not

Arguments:

    CString & strUserName   : User name which may contain a domain name
    CString & strDomainName : Output domain name ("." if local)

Return Value:

    TRUE if a domain is split off

--*/
{
    //
    // Assume local
    //
    strDomainName = _T(".");
    int nSlash = strUserName.Find(_T("\\"));
    if (nSlash >= 0)
    {
        strDomainName = strUserName.Left(nSlash);
        strUserName = strUserName.Mid(nSlash + 1);

        return TRUE;
    }

    return FALSE;
}






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(FALSE && "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;


//
// 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
//
CString * CINumber::s_pstrThousandSeperator = NULL;
CString * CINumber::s_pstrDecimalPoint = NULL;
CString * CINumber::s_pstrBadNumber = NULL;
CString * CINumber::s_pstrCurrency = NULL;
CString * CINumber::s_pstr = NULL;
BOOL CINumber::s_fAllocated = FALSE;
BOOL CINumber::s_fCurrencyPrefix = TRUE;
BOOL CINumber::s_fInitialized = FALSE;



#ifdef _DOS



BOOL
_dos_getintlsettings(
    OUT INTLFORMAT * pStruct
    )
/*++

Routine Description:

    Get the international settings on a DOS box

Parameters:

    INTLFORMAT * pStruct : Structure to be filled in.

Return Value:

    TRUE for success, FALSE for failure

--*/
{
    TRACEEOLID("[_dos_getintlsetting]");
    union _REGS inregs, outregs;
    struct _SREGS segregs;

    inregs.h.ah = 0x38;   // Intl call
    inregs.h.al = 0x00;   // Current country code
    inregs.x.bx = 0x00;   // Current country code

    segregs.ds  = _FP_SEG(pStruct);
    inregs.x.dx = _FP_OFF(pStruct);

    int nError = _intdosx(&inregs, &outregs, &segregs);

    return outregs.x.cflag == 0;
}

#endif // _DOS



/* protected */
CINumber::CINumber()
/*++

Routine Description:

    Constructor

Arguments:

    None

Return Value:

    None

--*/
{
    if (!CINumber::s_fInitialized)
    {
        CINumber::Initialize();
    }
}



CINumber::~CINumber()
/*++

Routine Description:

    Destructor

Arguments:

    None

Return Value:

    None

--*/
{
}



/* protected */
/* 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
        {
            CINumber::s_pstrThousandSeperator = new CString(_T(","));
            CINumber::s_pstrDecimalPoint = new CString(_T("."));
            CINumber::s_pstrBadNumber = new CString(_T("--"));
            CINumber::s_pstrCurrency = new CString(_T("$ "));
            CINumber::s_pstr = new CString;
            s_fAllocated = TRUE;
        }
        catch(CMemoryException * e)
        {
            TRACEEOLID("Initialization Failed");
            e->ReportError();
            e->Delete();
        }
    }

    LowerThreadProtection();

    return IsAllocated();
}



/* protected */
/* static */
void
CINumber::DeAllocate()
/*++

Routine Description:

    Clean up allocations

Arguments:

    N/A

Return Value:

    N/A

--*/
{
    RaiseThreadProtection();
    
    ASSERT(IsAllocated());
    if (IsAllocated())
    {
        MAKE_NULL(CINumber::s_pstrThousandSeperator);
        MAKE_NULL(CINumber::s_pstrDecimalPoint);
        MAKE_NULL(CINumber::s_pstrBadNumber);
        MAKE_NULL(CINumber::s_pstrCurrency);
        MAKE_NULL(CINumber::s_pstr);
    }

    LowerThreadProtection();

    s_fAllocated = FALSE;
}



/* static */
/* protected */
BOOL
CINumber::Initialize(
    IN BOOL fUserSetting /* TRUE */
    )
/*++

Routine Description:

    Initialize all the international settings, such as thousand
    seperators 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
    {

#if defined(_MAC)

        TRACEEOLID("Couldn't get international settings from system");

#elif defined(_WIN32)

        LCID lcid = fUserSetting
            ? ::GetUserDefaultLCID()
            : GetSystemDefaultLCID();

        LCTYPE lctype = fUserSetting ? 0 : LOCALE_NOUSEROVERRIDE;

        //
        // Get Decimal Point
        //
        if (!::GetLocaleInfo(
            lcid,
            LOCALE_SDECIMAL | lctype,
            CINumber::s_pstrDecimalPoint->GetBuffer(MAXLEN),
            MAXLEN
            ))
        {
            TRACEEOLID("Unable to get intl decimal point");
            ++cErrors;
        }

        CINumber::s_pstrDecimalPoint->ReleaseBuffer();

        //
        // Get Thousand Seperator
        //
        if (!::GetLocaleInfo(
            lcid, LOCALE_STHOUSAND | lctype,
            CINumber::s_pstrThousandSeperator->GetBuffer(MAXLEN),
            MAXLEN
            ))
        {
            TRACEEOLID("Unable to get 1000 seperator");
            ++cErrors;
        }

        CINumber::s_pstrThousandSeperator->ReleaseBuffer();

#ifndef _UNICODE

        //
        // Some countries have a space as a 1000 seperator,
        // 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::s_pstrThousandSeperator)[0] == CHAR(160))
        {
            CINumber::s_pstrThousandSeperator->SetAt(0, ' ');
            TRACEEOLID("Space 1000 seperator substituted");
        }

#endif // _UNICODE

        //
        // Get currency symbol
        //
        if (!::GetLocaleInfo(
            lcid,
            LOCALE_SCURRENCY | lctype,
            CINumber::s_pstrCurrency->GetBuffer(MAXLEN),
            MAXLEN
            ))
        {
            TRACEEOLID("Unable to get currency symbol");
            ++cErrors;
        }

        CINumber::s_pstrCurrency->ReleaseBuffer();

#elif defined(_WIN16)

        //
        // Get Decimal Point
        //
        ::GetProfileString(
            "Intl",
            "sDecimal",
            ".",
            CINumber::s_pstrDecimalPoint->GetBuffer(MAXLEN),
            MAXLEN
            );
        CINumber::s_pstrDecimalPoint->ReleaseBuffer();

        //
        // Get 1000 seperator
        //
        ::GetProfileString(
            "Intl",
            "sThousand",
            ",",
            CINumber::s_pstrThousandSeperator->GetBuffer(MAXLEN),
            MAXLEN
            );
        CINumber::s_pstrThousandSeperator->ReleaseBuffer();

        //
        // Get currency symbol
        //
        ::GetProfileString(
            "Intl",
            "sCurrency",
            ",",
            CINumber::s_pstrCurrency->GetBuffer(MAXLEN),
            MAXLEN
            );
        CINumber::s_pstrCurrency->ReleaseBuffer();

#elif defined(_DOS)

        INTLFORMAT fm;

        if (_dos_getintlsettings(&fm))
        {
            //
            // Get Decimal Point
            //
            *CINumber::s_pstrDecimalPoint = fm.szDecimalPoint;

            //
            // Get 1000 seperator
            //
            *CINumber::s_pstrThousandSeperator = fm.szThousandSep;

            //
            // Get Currency Symbol
            //
            *CINumber::s_pstrCurrency = fm.szCurrencySymbol;
        }
        else
        {
            TRACEEOLID("Unable to get intl settings");
            ++cErrors;
        }

#endif // _WIN32 etc

    }

    catch(CMemoryException * e)
    {
        TRACEEOLID("!!!exception in getting intl settings:");
        e->ReportError();
        e->Delete();
        ++cErrors;
    }

    TRACEEOLID("Thousand Seperator . . . . . : " << *CINumber::s_pstrThousandSeperator);
    TRACEEOLID("Decimal Point  . . . . . . . : " << *CINumber::s_pstrDecimalPoint);
    TRACEEOLID("Currency Symbol. . . . . . . : " << *CINumber::s_pstrCurrency);
    TRACEEOLID("Bad number . . . . . . . . . : " << *CINumber::s_pstrBadNumber);
    TRACEEOLID("Currency Prefix. . . . . . . : " << CINumber::s_fCurrencyPrefix);

    CINumber::s_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 seperators

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 seperator is 1 character.
        //
        ASSERT(CINumber::s_pstrThousandSeperator->GetLength() == 1);
        if (lNum != 0L && (outstrlen == 3 || outstrlen == 7 || outstrlen == 11))
        {
            lstrcpy(lpOutString + outstrlen, *CINumber::s_pstrThousandSeperator);
            outstrlen += CINumber::s_pstrThousandSeperator->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::s_pstrDecimalPoint + lpFraction;

    return (LPCTSTR)str;
}



/* static */
BOOL
CINumber::ConvertStringToLong(
    IN  LPCTSTR lpsrc,
    OUT LONG & lValue
    )
/*++

Routine Description:

    Convert string to long integer.  1000 Seperators 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 seperator is only 1 char.  See note below
    //
    ASSERT(CINumber::s_pstrThousandSeperator->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 seperator?
        // CAVEAT: If a thousand seperator of more than
        //         one character is used, this won't work.
        //
        else if ((strNumber[i] != (*CINumber::s_pstrThousandSeperator)[0])
             || (i != 3) && (i != 7) && (i != 11))
        {
            //
            // This is just invalid, since it is not a thousand
            // seperator 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::s_pstrDecimalPoint->GetLength() == 1);

    //
    // Strip off the > 0 part
    //
    LONG lFraction = 0;

    int nPoint = strNumber.ReverseFind((*CINumber::s_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
    )
{
    if (lValue != 0)
    {
        m_lValue /= lValue;
    }

    return *this;
}



//
// As above
//
CILong &
CILong::operator /=(
    IN const LPCTSTR lpszValue
    )
{
    LONG lValue;

    ConvertStringToLong(lpszValue, lValue);

    if (0 == lValue)
    {
        m_lValue = 0;
    }
    else
    {
        m_lValue /= lValue;
    }

    return *this;
}



//
// As above
//
CILong &
CILong::operator /=(
    IN const CILong& value
    )
{
    m_lValue /= value.m_lValue;

    return *this;
}



CIFloat::CIFloat(
    IN int nPrecision
    )
/*++

Routine Description:

    Constructor without arguments

Parameters:

    int nPrecision              Number of decimal digits in string,

Return Value:

    N/A

--*/
    : m_flValue(0.0),
      m_nPrecision(nPrecision)
{
}



CIFloat::CIFloat(
    IN double flValue,
    IN int nPrecision
    )
/*++

Routine Description:

    Constructor taking double argument

Parameters:

    double flValue              Value to be set
    int nPrecision              Number of decimal digits in string,

Return Value:

    N/A

--*/
    : m_flValue(flValue),
      m_nPrecision(nPrecision)
{
}



CIFloat::CIFloat(
    IN LONG lInteger,
    IN LONG lFraction,
    IN int nPrecision
    )
/*++

Routine Description:

    Constructor taking integer and fraction argument

Parameters:

    LONG lInteger               Integer portion
    LONG lFraction              Fractional portion
    int nPrecision              Number of decimal digits in string,

Return Value:

    N/A

--*/
    : m_nPrecision(nPrecision)
{
    m_flValue = CINumber::BuildFloat(lInteger, lFraction);
}



CIFloat::CIFloat(
    IN LPCTSTR lpszValue,
    IN int nPrecision
    )
/*++

Routine Description:

    Constructor taking string argument

Parameters:

    LPCTSTR lpszValue           String number
    int nPrecision              Number of decimal digits in string,

Return Value:

    N/A

--*/
    : m_nPrecision(nPrecision)
{
    ConvertStringToFloat(lpszValue, m_flValue);
}



CIFloat &
CIFloat::operator =(
    IN double flValue
    )
/*++

Routine Description:

    Assignment operator taking double value

Parameters:

    double flValue     Value to be set

Return Value:

    this object

--*/
{
    m_flValue = flValue;

    return *this;
}



CIFloat &
CIFloat::operator =(
    IN LPCTSTR lpszValue
    )
/*++

Routine Description:

    Assignment operator taking string value

Parameters:

    LPCTSTR lpszValue       String number

Return Value:

    this object

--*/
{
    ConvertStringToFloat(lpszValue, m_flValue);

    return *this;
}



//
// Arithmetic Shorthand operators
//
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<



CIFloat &
CIFloat::operator +=(
    IN const double flValue
    )
{
    m_flValue += flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator +=(
    IN const LPCTSTR lpszValue
    )
{
    double flValue;

    ConvertStringToFloat(lpszValue, flValue);

    m_flValue += flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator +=(
    IN const CIFloat& value
    )
{
    m_flValue += value.m_flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator -=(
    IN const double flValue
    )
{
    m_flValue -= flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator -=(
    IN const LPCTSTR lpszValue
    )
{
    double flValue;

    ConvertStringToFloat(lpszValue, flValue);

    m_flValue -= flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator -=(
    IN const CIFloat& value
    )
{
    m_flValue -= value.m_flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator *=(
    IN const double flValue
    )
{
    m_flValue *= flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator *=(
    IN const LPCTSTR lpszValue
    )
{
    double flValue;

    ConvertStringToFloat(lpszValue, flValue);

    m_flValue *= flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator *=(
    IN const CIFloat& value
    )
{
    m_flValue *= value.m_flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator /=(
    IN const double flValue
    )
{
    m_flValue /= flValue;

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator /=(
    IN const LPCTSTR lpszValue
    )
{
    double flValue;

    ConvertStringToFloat(lpszValue, flValue);
    if (flValue != 0)
    {
        m_flValue /= flValue;
    }

    return *this;
}



//
// As above
//
CIFloat &
CIFloat::operator /=(
    IN const CIFloat& value
    )
{
    m_flValue /= value.m_flValue;

    return *this;
}