Click here to Skip to main content
15,892,298 members
Articles / Programming Languages / C++

SS_Log: Fast program logging and tracing class

Rate me:
Please Sign up or sign in to vote.
4.84/5 (32 votes)
24 Apr 200321 min read 426.4K   4.1K   125  
Easily trace and log program flow. Allows multiple filters, multiple destinations (file, nt event log, etc), and it's fast!
// ----------------------------------------------------------------------- //
///
/// @file       SS_String.cpp
/// @author     Steve Schaneville
/// @date       27 Nov 2001, 09:44
/// @brief      Implementation file for the SS_String class.
///
///  
///
/// Copyright (c) 2001 - 2003\n
/// z_web_master@yahoo.com\n
/// http://schaneville.webhop.net/SS_Utils
///
// ----------------------------------------------------------------------- //

// ------------------[       Pre-Include Defines       ]------------------ //
// ------------------[          Include Files          ]------------------ //
#include "SS_String.h"

// ------------------[      Macros/Constants/Types     ]------------------ //

/// A simple verify macro used in several functions, just to save typing.
#define VERIFY_POINTER(p)           \
{                                   \
    if( !p )                        \
    {                               \
        ASSERT(!_T("bad pointer")); \
        return NULL;                \
    }                               \
}

// ------------------[         Global Variables        ]------------------ //
// ------------------[         Global Functions        ]------------------ //
// ------------------[    Class Function Definitions   ]------------------ //

// ----------------------------------------------------------------------- //
/// The default constructor.
///
/// @fn	        SS_String::SS_String()
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::SS_String()
{
    InitObject();
}

// ----------------------------------------------------------------------- //
/// This is the destructor.  We will ASSERT if any outstanding references
/// remain on this instance of the SS_String.  In release builds, we will
/// simply ignore outstanding references.
///
/// @fn	        SS_String::~SS_String()
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::~SS_String()
{
    delete [] m_szString;
    m_szString = NULL;
    BufferSize(0);
    FreeTokens();
    if( References() )
        ASSERT(!_T("There should be no references outstanding!!  You should ")
                _T("call Release() before allowing this instance to be ")
                _T("destructed"));
}

// ----------------------------------------------------------------------- //
/// @fn	        SS_String::SS_String(const SS_String& ssString)
/// @param		ssString [in] the SS_String instance to create a copy of.
/// @return     The instance of the newly created string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::SS_String( const SS_String& ssString )
{
    InitObject();
    *this = ssString;
}

// ----------------------------------------------------------------------- //
/// @fn	        SS_String::SS_String(LPCTSTR szString)
/// @param		szString [in] a null-terminated string used to initialize 
///                           our new SS_String instance.
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::SS_String( LPCTSTR szString )
{
    InitObject();
    *this = szString;
}

// ----------------------------------------------------------------------- //
/// If we are in an ANSI application, the BSTR will be converted to an ANSI 
/// string before initialization.
///
/// @fn	        SS_String::SS_String(BSTR bstrString)
/// @param		bstrString [in] a null-terminated BSTR string used to 
///                             initialize our new SS_String instance.
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::SS_String( BSTR bstrString )
{
    InitObject();
    *this = bstrString;
}

// ----------------------------------------------------------------------- //
/// The string will be initialized to a text representation of the integer 
/// passed into the constructor.
///
/// @fn	        SS_String::SS_String(INT nValue)
/// @param		nValue [in] an integer value used to initialize our new 
///                         SS_String instance.  
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::SS_String(INT nValue)
{
    InitObject();
    TCHAR szTmp[SS_STR_UNIT_LEN];
    *this = _itot(nValue, szTmp, 10);
}

// ----------------------------------------------------------------------- //
/// @fn	        SS_String::SS_String(HINSTANCE hInstance, UINT nResourceID)
/// @param		hInstance [in] the instance of the windows process, passed
///                            into the WinMain (or main, or DllMain) 
///                              function.
/// @param		nResourceID [in] a string resource ID from which we will 
///                              initialize our string.
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::SS_String(HINSTANCE hInstance, UINT nResourceID)
{
    InitObject();
    LoadString(hInstance, nResourceID);
}

#if defined(UNICODE) || defined(_UNICODE)
// ----------------------------------------------------------------------- //
/// A constructor that takes a const char* in UNICODE applications.
///
/// @fn	        SS_String::SS_String(const char* szString)
/// @param		szString [in] a null-terminated BSTR string used to 
///                           initialize our new SS_String instance.
/// @return     none
///
/// @note This constructor will only be defined if we are in a UNICODE 
/// application.  The ANSI string passed into this constructor will be 
/// converted into a UNICODE string before initialization.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::SS_String(const char* szString)
{
    InitObject();
    *this = szString;
}
#endif


// ----------------------------------------------------------------------- //
/// The default assignment operator.
///
/// @fn	        SS_String::operator = ( const SS_String& rhs )
/// @param		rhs [in] a null-terminated BSTR string used to 
///                      initialize our new SS_String instance.
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::operator = ( const SS_String& rhs )
{
    if( References() )
        // we do this only to check and see if the string will
        // be re-initialized.  If it will be (and we have outstanding
        // references), the function below will cause an ASSERT.  Note
        // that the user should call ReleaseBuffer() if this situation
        // arises.
        String(rhs.String());

    delete [] m_szString;
    m_szString = NULL;
    FreeTokens();
    InitObject();
    String(rhs.String());

    return *this;
}

// ----------------------------------------------------------------------- //
///	This function should be called from all constructors BEFORE initializing
/// any variables in the constructor.
///
/// @fn	        SS_String::InitObject()
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
VOID SS_String::InitObject()
{
    m_szString = NULL;
    m_b_ss_tcstok_trailingToken = FALSE;
    m_sz_ss_tcstok_pos = 0;
    BufferSize(0);
    References(0);
    Tokens(NULL);
    TokenCount(0);
    CurrentToken(-1);
    String(_T(""));
    TokensAreValid(FALSE);
}

// ----------------------------------------------------------------------- //
///	This is the function that most assignment operators map to.  It will
/// take a string and copy it into out local buffer.
///
/// @fn	        SS_String::String(LPCTSTR szString)
/// @param		szString [in] null-terminated string to copy into the buffer
///							  of our SS_String instance.
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
VOID SS_String::String(LPCTSTR szString)
{
    if( szString )
    {
        if( InitString(_tcslen(szString)) )
            _tcscpy(m_szString, szString);
    }
    else
    {
        String(_T(""));
    }
}

// ----------------------------------------------------------------------- //
///	This function will take the contents of the BSTR and copy them into our
/// local buffer.  If we are in a UNICODE application, this function will
/// convert the BSTR into an ANSI string before copying it into the local
/// buffer.
///
/// @fn	        SS_String::String(const BSTR bstrString)
/// @param		bstrString [in] BSTR string to copy into the buffer
///							    of our SS_String instance.
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
VOID SS_String::String(const BSTR bstrString)
{
    INT nLength = 0;

    if( bstrString )
        nLength = InitString(wcslen(bstrString));
    else
        nLength = InitString(0);

    if( bstrString )
    {
#if defined( UNICODE ) || defined( _UNICODE )
        _tcscpy(m_szString, bstrString);
#else
        wcstombs(m_szString, bstrString, nLength);
#endif
    }
}

#if defined(UNICODE) || defined(_UNICODE)
// ----------------------------------------------------------------------- //
///	This function will convert the contents of the szString from ANSI to
/// UNICODE and then copy the contents into the local buffer.  
///
/// @fn	        SS_String::String(const char* szString)
/// @param		szString [in] null-terminated string to copy into the buffer
///							  of our SS_String instance.
/// @return     none
///
/// @note This function is only defined in UNICODE applications.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
VOID SS_String::String(const char* szString)
{
    INT nLength = InitString(strlen(szString));
    if( nLength )
        mbstowcs(m_szString, szString, nLength);
}
#endif

// ----------------------------------------------------------------------- //
///	Given a minimum desired buffer size, this function allocates a new buffer 
/// for our string that is a power of 2 of the SS_STR_UNIT_LEN.  An additional
/// byte is allocated for the null terminator.
///
/// @fn	        SS_String::InitString(INT nLength)
/// @param		nLength [in] the minimum length of the new buffer size
///                          desired for the string, not including
///                          the null terminator (which will be
///                          accounted for automatically).
/// @return     Size of the new buffer created, or 0 if failed.
///
/// @note The function checks for outstanding references before allowing the 
/// re-allocation and returns no new allocation if any remain outstanding.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::InitString(INT nLength)
{
	ASSERT(nLength >= 0);
    nLength++; // add 1 for our null terminator to be safe
	INT nNewLength = SS_STR_UNIT_LEN;
	while( nNewLength < nLength )
		nNewLength *= 2;
    if( nNewLength > BufferSize() )
    {
        if( References() )
        {
            // if we get here, we are about to delete the string while there
            // are still references to the string somewhere (presumably
            // aquired with the GetBuffer() functions).  You must first call
            // ReleaseBuffer() before re-initializing this string.
            ASSERT(!_T("References remain open on this SS_String instance.  Call ")
                    _T("ReleaseBuffer() before allowing a string re-initialization."));
            return 0;
        }

        delete [] m_szString;
        BufferSize( nNewLength );
        m_szString = new TCHAR[BufferSize()];
        VERIFY_POINTER(m_szString);
    }
    _tcscpy(m_szString, _T(""));
    TokensAreValid(FALSE);
    return BufferSize();
}

// ----------------------------------------------------------------------- //
///	Case-sensitive comparison function for a BSTR.  For ANSI applications, 
/// this function will first convert the BSTR to an ANSI string before the 
/// comparison.
///
/// @fn	        SS_String::operator == (const BSTR rhs) const
/// @param		rhs [in] BSTR string to be compared to our local buffer
/// @return     TRUE if the local buffer and the BSTR are identical.
///	@return		FALSE if the local buffer and the BSTR differ.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
BOOL SS_String::operator == (const BSTR rhs) const
{
#if defined(UNICODE) || defined(_UNICODE)
    return _tcscmp(m_szString, rhs) == 0 ? TRUE : FALSE;
#else
    char* szRhs = new char[wcslen(rhs) + 1];
    VERIFY_POINTER(szRhs);
    wcstombs(szRhs, rhs, wcslen(rhs)+1);
    BOOL bResult = _tcscmp(m_szString, szRhs) == 0 ? TRUE : FALSE;
    delete [] szRhs;
    return bResult;
#endif
}

// ----------------------------------------------------------------------- //
///	Case-sensitive "not equals" function for a BSTR.  For ANSI applications, 
/// this function will first convert the BSTR to an ANSI string before the 
/// comparison.
///
/// @fn	        SS_String::operator != (const BSTR rhs) const
/// @param		rhs [in] BSTR string to be compared to our local buffer
/// @return     TRUE if the local buffer and the BSTR differ.
///	@return		FALSE if the local buffer and the BSTR are identical.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
BOOL SS_String::operator != (const BSTR rhs) const
{
#if defined(UNICODE) || defined(_UNICODE)
    return _tcscmp(m_szString, rhs) != 0 ? TRUE : FALSE;
#else
    char* szRhs = new char[wcslen(rhs) + 1];
    VERIFY_POINTER(szRhs);
    wcstombs(szRhs, rhs, wcslen(rhs)+1);
    BOOL bResult = _tcscmp(m_szString, szRhs) != 0 ? TRUE : FALSE;
    delete [] szRhs;
    return bResult;
#endif
}

// ----------------------------------------------------------------------- //
///	Case-sensitive "greater than or equals" function for a BSTR.  For ANSI 
/// applications, this function will first convert the BSTR to an ANSI string 
/// before the comparison.
///
/// @fn	        SS_String::operator >= (const BSTR rhs) const
/// @param		rhs [in] BSTR string to be compared to our local buffer
/// @return     TRUE if the local buffer is greater than or equal to
///				the passed in BSTR.
///	@return		FALSE if the local buffer is less than the BSTR.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
BOOL SS_String::operator >= (const BSTR rhs) const
{
#if defined(UNICODE) || defined(_UNICODE)
    return _tcscmp(m_szString, rhs) >= 0 ? TRUE : FALSE;
#else
    char* szRhs = new char[wcslen(rhs) + 1];
    VERIFY_POINTER(szRhs);
    wcstombs(szRhs, rhs, wcslen(rhs)+1);
    BOOL bResult = _tcscmp(m_szString, szRhs) >= 0 ? TRUE : FALSE;
    delete [] szRhs;
    return bResult;
#endif
}

// ----------------------------------------------------------------------- //
///	Case-sensitive "less than or equals" function for a BSTR.  For ANSI 
/// applications, this function will first convert the BSTR to an ANSI string 
/// before the comparison.
///
/// @fn	        SS_String::operator <= (const BSTR rhs) const
/// @param		rhs [in] BSTR string to be compared to our local buffer
/// @return     TRUE if the local buffer is less than or equal to
///				the passed in BSTR.
///	@return		FALSE if the local buffer is greater than the BSTR.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
BOOL SS_String::operator <= (const BSTR rhs) const
{
#if defined(UNICODE) || defined(_UNICODE)
    return _tcscmp(m_szString, rhs) <= 0 ? TRUE : FALSE;
#else
    char* szRhs = new char[wcslen(rhs) + 1];
    VERIFY_POINTER(szRhs);
    wcstombs(szRhs, rhs, wcslen(rhs)+1);
    BOOL bResult = _tcscmp(m_szString, szRhs) <= 0 ? TRUE : FALSE;
    delete [] szRhs;
    return bResult;
#endif
}

// ----------------------------------------------------------------------- //
///	Case-sensitive "greater than" function for a BSTR.  For ANSI 
/// applications, this function will first convert the BSTR to an ANSI string 
/// before the comparison.
///
/// @fn	        SS_String::operator > (const BSTR rhs) const
/// @param		rhs [in] BSTR string to be compared to our local buffer
/// @return     TRUE if the local buffer is greater than the passed in BSTR.
///	@return		FALSE if the local buffer is not greater than the BSTR.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
BOOL SS_String::operator > (const BSTR rhs) const
{
#if defined(UNICODE) || defined(_UNICODE)
    return _tcscmp(m_szString, rhs) > 0 ? TRUE : FALSE;
#else
    char* szRhs = new char[wcslen(rhs) + 1];
    VERIFY_POINTER(szRhs);
    wcstombs(szRhs, rhs, wcslen(rhs)+1);
    BOOL bResult = _tcscmp(m_szString, szRhs) > 0 ? TRUE : FALSE;
    delete [] szRhs;
    return bResult;
#endif
}

// ----------------------------------------------------------------------- //
///	Case-sensitive "less than" function for a BSTR.  For ANSI 
/// applications, this function will first convert the BSTR to an ANSI string 
/// before the comparison.
///
/// @fn	        SS_String::operator < (const BSTR rhs) const
/// @param		rhs [in] BSTR string to be compared to our local buffer
/// @return     TRUE if the local buffer is less than the passed in BSTR.
///	@return		FALSE if the local buffer is not less than the BSTR.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
BOOL SS_String::operator < (const BSTR rhs) const
{
#if defined(UNICODE) || defined(_UNICODE)
    return _tcscmp(m_szString, rhs) < 0 ? TRUE : FALSE;
#else
    char* szRhs = new char[wcslen(rhs) + 1];
    VERIFY_POINTER(szRhs);
    wcstombs(szRhs, rhs, wcslen(rhs)+1);
    BOOL bResult = _tcscmp(m_szString, szRhs) < 0 ? TRUE : FALSE;
    delete [] szRhs;
    return bResult;
#endif
}

#if !defined(UNICODE) || !defined(_UNICODE)
// ----------------------------------------------------------------------- //
///	BSTR cast function.  This function will convert the local buffer into 
/// a BSTR and return it.  
///
/// @fn	        SS_String::operator BSTR ()
/// @return     none
///
/// @note The returned BSTR is allocated with SysAllocString.  The user
/// is expected to free this memory with the SysFreeString function or
/// a memory leak will result.
///
/// @note This function is only defined in non-UNICODE applications.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::operator BSTR ()
{
    WCHAR* wszTmp = new WCHAR[BufferSize()];
    VERIFY_POINTER(wszTmp);
    mbstowcs(wszTmp, String(), _tcslen(String())+1);
    BSTR bstrString = SysAllocString(wszTmp);
    delete [] wszTmp;
    return bstrString;
}
#endif

// ----------------------------------------------------------------------- //
///	DOUBLE cast function.  This function will use the atof c-runtime function
/// to convert our string to a DOUBLE.  See the atof documentation for more
/// details.
///
/// @fn	        SS_String::operator DOUBLE ()
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::operator DOUBLE ()
{
#if defined(UNICODE) || defined(_UNICODE)
    double dResult = 0;
    char* szNum = new char[BufferSize()];
    VERIFY_POINTER(szNum);
    wcstombs(szNum, String(), wcslen(String())+1);
    dResult = atof(szNum);
    delete [] szNum;
    return dResult;
#else
    return atof(String());
#endif
}

// ----------------------------------------------------------------------- //
///	FLOAT cast function.  This function will use the atof c-runtime function
/// to convert our string to a FLOAT.  See the atof documentation for more
/// details.
///
/// @fn	        SS_String::operator FLOAT ()
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String::operator FLOAT ()
{
    return (FLOAT)((DOUBLE)*this);
}

// ----------------------------------------------------------------------- //
///	Addition operator.  This function concatenates the string on the right
/// side of the + operator with the string on the left and returns the result.  
/// Note that this function does not change either of the original strings.
///
/// @fn	        SS_String::operator + (LPCTSTR rhs)
/// @param      rhs [in] a null-terminated string buffer to concatenate with 
///                      the string on the left side of the + operator.
/// @return     An SS_String containing the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::operator + (LPCTSTR rhs)
{
    ASSERT(rhs);
    SS_String ssResult = *this;
    return ssResult += rhs;
}

// ----------------------------------------------------------------------- //
///	BSTR Addition operator.  This function concatenates the contents of the 
/// BSTR on the right side of the + operator with the string on the left and 
/// returns the result.  This function does not change either of 
/// the original strings.  In ANSI applications, the BSTR will be converted
/// to an ANSI string before the concatenation.
///
/// @fn	        SS_String::operator + (const BSTR rhs)
/// @param      rhs [in] a BSTR to concatenate with the string on the
///                      left side of the + operator.
/// @return     An SS_String containing the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::operator + (const BSTR rhs)
{
    ASSERT(rhs);
    SS_String ssResult = *this;
    return ssResult += rhs;
}

// ----------------------------------------------------------------------- //
///	Addition operator.  This function concatenates the contents of the 
/// SS_String on the right side of the + operator with the string on the left
/// and returns the result.  This function does not change either of 
/// the original strings.
///
/// @fn	        SS_String::operator + (const SS_String& rhs)
/// @param      rhs [in] an SS_String to concatenate with the string on the
///                      left side of the + operator.
/// @return     An SS_String containing the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::operator + (const SS_String& rhs)
{
    SS_String ssResult = *this;
    return ssResult += rhs.String();
}

// ----------------------------------------------------------------------- //
///	Addition assignment operator.  This function concatenates the contents of 
/// the null-terminated string pointer with the string on the left and 
/// returns the left SS_String instance.  This function permanently 
/// changes the string on the left of the += operator to contain the 
/// concatenated result.
///
/// @fn	        SS_String::operator += (LPCTSTR rhs)
/// @param      rhs [in] a null-terminated string to concatenate with the 
///                      string on the left side of the + operator.
/// @return     The original instance of the SS_String, which now contains 
///             the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::operator += (LPCTSTR rhs)
{
    TCHAR* szTmp = new TCHAR[BufferSize()];
    if(!szTmp)
    {
        ASSERT(!_T("memory allocation failed"));
        return *this;
    }
    _tcscpy(szTmp, String());
    InitString(_tcslen(szTmp)+_tcslen(rhs));
    _tcscpy(m_szString, szTmp);
    _tcscat(m_szString, rhs);
    delete [] szTmp;
    return *this;
}

// ----------------------------------------------------------------------- //
///	BSTR Addition assignment operator.  This function concatenates the 
/// contents of the BSTR on the right side of the += operator with the 
/// string on the left and returns the original SS_String instance.  This 
/// function permanently changes the original SS_String instance to 
/// contain the concatenated result.  Note that in ANSI applications,
/// this function will convert the BSTR to an ANSI string before the 
/// concatenation.
///
/// @fn	        SS_String::operator += (const BSTR rhs)
/// @param      rhs [in] a BSTR string to concatenate with the string on the
///                      left side of the + operator.
/// @return     The left instance of the SS_String, which now contains 
///             the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::operator += (const BSTR rhs)
{
#if defined(UNICODE) || defined(_UNICODE)
    return *this += (LPCTSTR)rhs;
#else
    TCHAR* szRhs = new TCHAR[wcslen(rhs)+1];
    if(!szRhs)
    {
        ASSERT(!_T("memory allocation failed"));
        return *this;
    }
    wcstombs(szRhs, rhs, wcslen(rhs)+1);
    *this += szRhs;
    delete [] szRhs;
    return *this;
#endif
}

// ----------------------------------------------------------------------- //
///	Addition assignment operator.  This function concatenates the contents of 
/// the SS_String on the right side of the += operator with the string on the
/// left and returns the left SS_String instance.  This function permanently 
/// changes the string on the left of the += operator to contain the 
/// concatenated result.
///
/// @fn	        SS_String::operator += (const SS_String& rhs)
/// @param      rhs [in] an SS_String to concatenate with the string on the
///                      left side of the + operator.
/// @return     The left instance of the SS_String, which now contains 
///             the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::operator += (const SS_String& rhs)
{
    *this += rhs.String();
    return *this;
}


// utilities
//
VOID SS_String::FreeTokens()
{
    delete [] Tokens();
    Tokens(NULL);
    TokenCount(0);
    CurrentToken(-1);
    TokensAreValid(FALSE);
}

// ----------------------------------------------------------------------- //
///	This function will return the token stored at the index passed in the
/// nTokenIndex parameter.  Each tokens consists of the text between 
/// any of the delimiters passed into the szDelimiters parameter.  Delimiters
/// must be passed into the first call to this function, but future calls
/// can leave this parameter null.  
///
/// If the local string buffer changes after a call to one of the tokens
/// functions is made, all stored tokens will become invalid, and further
/// calls to this function will fail unless a new set of delimiters are
/// passed in.
///
/// @fn	        SS_String::Token(INT nTokenIndex, LPCTSTR szDelimiters)
/// @param      nTokenIndex [in] zero-based index of the token desired in 
///                              the return value.
/// @param      szDelimiters [in] null-terminated string containing a list
///                               of delimiting characters.  Each of these
///                               characters will be used to break up the 
///                               local string buffer into subgroups.\n  
///								  \n
///                               If this parameter is null, the function
///                               will use delimiters previously passed
///                               into this function or other token functions
///                               that accept delimiters.\n
///								  \n
///                               If this parameter is not null, the function
///                               will parse the local buffer using these
///                               delimiters, and save the token results
///                               for future calls to token functions.
///                               
/// @return     The token at the index requested, or NULL if no token
///             is found at the index.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::Token(INT nTokenIndex, LPCTSTR szDelimiters /*=NULL*/)
{
    if( !TokensAreValid() || szDelimiters )
    {
        if( szDelimiters )
            InitTokens(szDelimiters);
        else
        {
            // MUST set new delimiters since string has changed
            // since last time we searched for the tokens.
            ASSERT(!_T("Must set new delimiters because local buffer changed"));
            return NULL;
        }
    }

    if( nTokenIndex < TokenCount() && nTokenIndex >= 0 )
    {
        CurrentToken(nTokenIndex);
        return Tokens()[nTokenIndex];
    }
    else
        return NULL;
}

// ----------------------------------------------------------------------- //
///	This function will return the first token stored found in the local 
/// buffer.  Each tokens consists of the text between any of the
/// delimiters passed into the szDelimiters parameter.  Delimiters
/// must be passed into the first call to this function, but future calls
/// can leave this parameter null.  
///
/// If the local string buffer changes after a call to one of the tokens
/// functions is made, all stored tokens will become invalid, and further
/// calls to this function will fail unless a new set of delimiters are
/// passed in.
///
/// @fn	        SS_String::FirstToken(LPCTSTR szDelimiters)
/// @param      szDelimiters [in] null-terminated string containing a list
///                               of delimiting characters.  Each of these
///                               characters will be used to break up the 
///                               local string buffer into subgroups.\n  
///								  \n
///                               If this parameter is null, the function
///                               will use delimiters previously passed
///                               into this function or other token functions
///                               that accept delimiters.\n
///								  \n
///                               If this parameter is not null, the function
///                               will parse the local buffer using these
///                               delimiters, and save the token results
///                               for future calls to token functions.
///                               
/// @return     The first token in the string, or NULL if no tokens
//              are found.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::FirstToken(LPCTSTR szDelimiters /*=NULL*/)
{
    if( !TokensAreValid() || szDelimiters )
    {
        if( szDelimiters )
            InitTokens(szDelimiters);
        else
        {
            // MUST set new delimiters since string has changed
            // since last time we searched for the tokens.
            ASSERT(!_T("Must set new delimiters because local buffer changed"));
            return 0;
        }
    }

    if( TokenCount() )
    {
        CurrentToken(0);
        return m_ppTokens[CurrentToken()];
    }
    else
    {
        CurrentToken(-1);
        return NULL;
    }
}

// ----------------------------------------------------------------------- //
///	This function will return the last token stored found in the local 
/// buffer.  Each tokens consists of the text between any of the
/// delimiters passed into the szDelimiters parameter.  Delimiters
/// must be passed into the first call to this function, but future calls
/// can leave this parameter null.  
///
/// If the local string buffer changes after a call to one of the tokens
/// functions is made, all stored tokens will become invalid, and further
/// calls to this function will fail unless a new set of delimiters are
/// passed in.
///
/// @fn	        SS_String::LastToken(LPCTSTR szDelimiters)
/// @param      szDelimiters [in] null-terminated string containing a list
///                               of delimiting characters.  Each of these
///                               characters will be used to break up the 
///                               local string buffer into subgroups.\n  
///								  \n
///                               If this parameter is null, the function
///                               will use delimiters previously passed
///                               into this function or other token functions
///                               that accept delimiters.\n
///								  \n
///                               If this parameter is not null, the function
///                               will parse the local buffer using these
///                               delimiters, and save the token results
///                               for future calls to token functions.
///                               
/// @return     The last token in the string, or NULL if no tokens
//              are found.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::LastToken(LPCTSTR szDelimiters /*=NULL*/)
{
    if( !TokensAreValid() || szDelimiters )
    {
        if( szDelimiters )
            InitTokens(szDelimiters);
        else
        {
            // MUST set new delimiters since string has changed
            // since last time we searched for the tokens.
            ASSERT(!_T("Must set new delimiters because local buffer changed"));
            return 0;
        }
    }

    if( TokenCount() )
    {
        CurrentToken(TokenCount()-1);
        return m_ppTokens[CurrentToken()];
    }
    else
    {
        CurrentToken(-1);
        return NULL;
    }
}

// ----------------------------------------------------------------------- //
///	This function parses this SS_String for any characters
/// that are in szDelimiters... if any are found, we store all
/// sections of the string between these found characters for
/// future use by the user (see strtok() in the msdn for
/// more information about the use of tokens).
///                               
/// @fn	        SS_String::InitTokens(LPCTSTR szDelimiters)
/// @param      szDelimiters [in] null-terminated string containing a list
///                               of delimiting characters.  Each of these
///                               characters will be used to break up the 
///                               local string buffer into subgroups.  
///
///                               If this parameter is null, the function
///                               will use delimiters previously passed
///                               into this function or other token functions
///                               that accept delimiters.
///
///                               If this parameter is not null, the function
///                               will parse the local buffer using these
///                               delimiters, and save the token results
///                               for future calls to token functions.
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
VOID SS_String::InitTokens(LPCTSTR szDelimiters)
{
    ASSERT(szDelimiters);

    // delete old stuff
    FreeTokens();

    // Create new temporary buffer
    LPTSTR szToken = NULL;
    LPTSTR szTokens = new TCHAR[BufferSize()];
    if(!szTokens)
    {
        ASSERT(!_T("memory allocation failed"));
        return;
    }
    _tcscpy(szTokens, String());

    // count tokens
    INT nCount = 0;
    szToken = _ss_tcstok(szTokens, szDelimiters);
    while(szToken)
    {
        nCount++;
        szToken = _ss_tcstok(NULL, szDelimiters);
    }

    // now create the space for the tokens
    Tokens( new SS_String[nCount] );
    TokenCount(nCount);

    // fill the tokens into the array
    _tcscpy(szTokens, String());
    szToken = _ss_tcstok(szTokens, szDelimiters);
    nCount = 0;
    while(szToken)
    {
        Tokens()[nCount] = szToken;
        szToken = _ss_tcstok(NULL, szDelimiters);
        nCount++;
    }

    delete [] szTokens;

    if( TokenCount() )
        TokensAreValid(TRUE);
}

// ----------------------------------------------------------------------- //
///	This function will return the next token found in the local 
/// buffer.  Each tokens consists of the text between any of the
/// delimiters passed into the szDelimiters parameter.  Delimiters
/// must be passed into the Token, FirstToken, or LastToken calls before this
/// function can be called.
///
/// If the local string buffer changes after a call to one of the tokens
/// functions is made, all stored tokens will become invalid, and further
/// calls to this function will fail unless a new set of delimiters are
/// passed in.
///
/// @fn	        SS_String::NextToken()
/// @return     The next token in the string, or NULL if no more tokens
///             are found.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::NextToken()
{
    ASSERT(TokensAreValid()); // you must first set the delimiters by
                              // calling FirstToken(), LastToken(), or
                              // Token().  You must do this every time
                              // this SS_String changes.

    if( CurrentToken() == TokenCount() )
        return NULL;

    if( TokenCount() )
        CurrentToken(CurrentToken()+1); // m_nCurrentToken++;

    if( CurrentToken() == TokenCount() )
        return NULL;

    return m_ppTokens[CurrentToken()];
}

// ----------------------------------------------------------------------- //
///	This function will return the previous token found in the local 
/// buffer.  Each tokens consists of the text between any of the
/// delimiters passed into the szDelimiters parameter.  Delimiters
/// must be passed into the Token, FirstToken, or LastToken calls before this
/// function can be called.
///
/// If the local string buffer changes after a call to one of the tokens
/// functions is made, all stored tokens will become invalid, and further
/// calls to this function will fail unless a new set of delimiters are
/// passed in.
///
/// @fn	        SS_String::NextToken()
/// @return     The previous token in the string, or NULL if no more tokens
///             are found.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::PreviousToken()
{
    ASSERT(TokensAreValid()); // you must first set the delimiters by
                              // calling FirstToken(), LastToken(), or
                              // Token().  You must do this every time
                              // this SS_String changes.

    if( -1 == CurrentToken() )
        return NULL;

    if( TokenCount() )
        CurrentToken(CurrentToken()-1); // m_nCurrentToken--;

    if( -1 == CurrentToken() )
        return NULL;

    return m_ppTokens[CurrentToken()];
}

// ----------------------------------------------------------------------- //
/// This function loads a string resource ID into the local buffer.
///
/// @fn	        SS_String::LoadString(HINSTANCE hInstance, UINT nResourceID)
/// @param      hInstance [in] the instance handle of this process, provided
///                            to the user in the WinMain functions.
/// @param      nResourceID [in] a string resource ID from which to load the
///                              string into the current buffer.
/// @return     This instance of the SS_String with the string resource ID
///             loaded.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::LoadString(HINSTANCE hInstance, UINT nResourceID)
{
    TCHAR szTmp[SS_STR_UNIT_LEN];
    INT nLen = ::LoadString(hInstance, nResourceID, szTmp, SS_STR_UNIT_LEN);
    if( 1 < SS_STR_UNIT_LEN - nLen )
    {
        *this = szTmp;
        return *this;
    }

    INT nSize = SS_STR_UNIT_LEN - 1;
    do
    {
        nSize += SS_STR_UNIT_LEN;
        nLen = ::LoadString(hInstance, nResourceID, GetBuffer(nSize), nSize);
        ReleaseBuffer();
    } while( 1 >= nSize - nLen);

    return *this;
}

// ----------------------------------------------------------------------- //
/// This function displays a message box with the current contents of our
/// SS_String object as the message.
///
/// @fn	        SS_String::MessageBox(HWND hWnd, LPCTSTR szTitle, UINT uType) const
/// @param      hWnd [in] parent window for the message box
/// @param      szTitle [in] a string to put into the title of the dialog.
///                          The default value is "Dialog".
/// @param      uType [in] one or more of the messagebox types (MB_OK, MB_YESNO, etc.)
///                        specified in the Windows API MessageBox function.  
///                        The default is MB_OK|MB_ICONINFORMATION.
/// @return     The user's response when closing the message box. 
/// @author     Steve Schaneville
///
// ----------------------------------------------------------------------- //
INT SS_String::MessageBox(HWND hWnd, LPCTSTR szTitle, UINT uType) const
{
    return ::MessageBox(hWnd, String(), szTitle, uType);
}

// ----------------------------------------------------------------------- //
/// This function provides the user with a writable pointer to the local
/// string buffer.  If nMinLength is larger than the current allocated buffer,
/// a new buffer with at least nMinLength bytes will be allocated and our
/// current string copied into it before returning.  
///
/// @fn	        SS_String::GetBuffer(INT nMinLength)
/// @param      nMinLength [in] minimum size of the returned buffer.
/// @return     A writable pointer to the local string buffer.
///
/// @note The user must call ReleaseBuffer once for each call to GetBuffer().
/// Any calls to functions that change the local buffer will fail until
/// all references to the local buffer have been released.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::GetBuffer(INT nMinLength)
{
    if( BufferSize() <= nMinLength )
    {
        SS_String ssTmp = *this;
        InitString(nMinLength);
        _tcscpy(m_szString, ssTmp);
    }

    AddReference();
    return m_szString;
}

LPCTSTR SS_String::GetBufferConst()
{
    return m_szString;
}

// ----------------------------------------------------------------------- //
/// Releases a previously obtained reference to the local buffer.  Call this 
/// function once for each call to the GetBuffer function.
///
/// @fn	        String::ReleaseBuffer()
/// @return     none
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
VOID SS_String::ReleaseBuffer()
{
    RemoveReference();
}

// ----------------------------------------------------------------------- //
/// Returns a string plus extra characters found in the local buffer, and
/// the zero-based index of the found string.  Note that there may 
/// be more characters in the returned string than passed in with the 
/// szFindStringparameter.  This is because the returned value is assigned
/// by simply pointing to the middle of our SS_String main
/// buffer, and all characters beyond the found sub-string
/// will still be present.  This behavior is by design.
///
/// @fn	        SS_String::Find(LPCTSTR szFindString, INT nStartPos, INT* pnFoundAtPos, BOOL bIgnoreCase)
/// @param      szFindString [in] a sub-string to look for in this SS_String.
/// @param      nStartPos [in] the zero-based position from which to start the 
///                            search.  Defaults to 0.
/// @param      pnFoundAtPos [out] returns the zero-based position of the 
///                                found string, or -1 if the string is not 
///                                found.  If this parameter is NULL (the
///                                default), no position is returned.
/// @param      bIgnoreCase [in] set to TRUE to perform a case-insensitive
///                              search.  Defaults to FALSE.
/// @return     The found string *plus* any remaining characters that are
///             in this SS_String.  
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::Find(LPCTSTR szFindString,
                          INT nStartPos /*=0*/,
                          INT* pnFoundAtPos /*=NULL*/,
                          BOOL bIgnoreCase /*=FALSE*/)
{
    ASSERT( nStartPos < BufferSize() );

    LPTSTR szCurrentPos = (LPTSTR)(String()+nStartPos);
    INT nLength = _tcslen(szFindString);
    BOOL bFound = FALSE;
    INT nCurrentPos = nStartPos;

    while( *szCurrentPos != '\0' )
    {
        if( bIgnoreCase )
        {
            if( !_tcsnicmp(szCurrentPos, szFindString, nLength) )
            {
                bFound = TRUE;
                break;
            }
        }
        else
        {
            if( !_tcsncmp(szCurrentPos, szFindString, nLength) )
            {
                bFound = TRUE;
                break;
            }
        }

        nCurrentPos++;
        szCurrentPos++;
    }

    if( bFound )
    {
        if( pnFoundAtPos )
            *pnFoundAtPos = nCurrentPos;

        SS_String ssResult = szCurrentPos;
        return ssResult;
    }
    else
    {
        if( pnFoundAtPos )
            *pnFoundAtPos = -1;

        SS_String ssResult;
        return ssResult;
    }
}

// ----------------------------------------------------------------------- //
/// Returns a string plus extra characters found in the local buffer, and
/// the zero-based index of the found string.  Note that there may 
/// be more characters in the returned string than passed in with the 
/// szFindStringparameter.  This is because the returned value is assigned
/// by simply pointing to the middle of our SS_String main
/// buffer, and all characters beyond the found sub-string
/// will still be present.  This behavior is by design.
///
/// @fn	        SS_String::FindReverse(LPCTSTR szFindString, INT nStartPos, INT* pnFoundAtPos, BOOL bIgnoreCase)
/// @param      szFindString [in] a sub-string to look for in this SS_String.
/// @param      nStartPos [in] the zero-based position from which to start the 
///                            reverse search.  Even though this is a reverse
///                            search, the nStartPos parameter is counted
///                            from the front of the string.  Pass in a 
///                            position of -1 (the default) to search from 
///                            the end of the local buffer.
/// @param      pnFoundAtPos [out] returns the zero-based position of the 
///                                found string, or -1 if the string is not 
///                                found.  If this parameter is NULL (the
///                                default), no position is returned.
/// @param      bIgnoreCase [in] set to TRUE to perform a case-insensitive
///                              search.  Defaults to FALSE.
/// @return     The found string *plus* any remaining characters that are
///             in this SS_String.  
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::FindReverse(LPCTSTR szFindString,
                                 INT nStartPos /*=-1*/,
                                 INT* pnFoundAtPos /*=NULL*/,
                                 BOOL bIgnoreCase /*=FALSE*/)
{
    ASSERT( nStartPos < BufferSize() );

    LPTSTR szCurrentPos = NULL;
    INT nCurrentPos = 0;
    if( -1 == nStartPos )
    {
        szCurrentPos = (LPTSTR)(String()+Length()-1);
        nCurrentPos = Length()-1;
    }
    else
    {
        szCurrentPos = (LPTSTR)(String()+nStartPos);
        nCurrentPos = nStartPos;
    }

    INT nLength = _tcslen(szFindString);
    BOOL bFound = FALSE;

    while( 0 <= nCurrentPos )
    {
        if( bIgnoreCase )
        {
            if( !_tcsnicmp(szCurrentPos, szFindString, nLength) )
            {
                bFound = TRUE;
                break;
            }
        }
        else
        {
            if( !_tcsncmp(szCurrentPos, szFindString, nLength) )
            {
                bFound = TRUE;
                break;
            }
        }

        nCurrentPos--;
        szCurrentPos--;
        if( 0 > nCurrentPos )
            break;
    }

    if( bFound )
    {
        if( pnFoundAtPos )
            *pnFoundAtPos = nCurrentPos;

        SS_String ssResult = szCurrentPos;
        return ssResult;
    }
    else
    {
        if( pnFoundAtPos )
            *pnFoundAtPos = -1;

        SS_String ssResult;
        return ssResult;
    }
}

// ----------------------------------------------------------------------- //
/// Returns a list of characters from the middle of the local buffer.
///
/// @fn	        SS_String::Mid(INT nFirst, INT nCount) const
/// @param      nFirst [in] the position of the first character to
///                         return from this SS_String.
/// @param      nCount [in] the number of characters to return.
/// @return     An SS_String containing nCount characters from the nFirst
///             position in our local buffer.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::Mid(INT nFirst, INT nCount) const
{
    ASSERT(nFirst < BufferSize());
    ASSERT(nCount < (BufferSize()-nFirst));
    SS_String ssResult = (LPCTSTR)(String()+nFirst);
    ssResult.SetAt(nCount, '\0');
    return ssResult;
}

// ----------------------------------------------------------------------- //
/// Returns a list of characters from the beginning of the local buffer.
///
/// @fn	        SS_String::Left(INT nCount) const
/// @param      nCount [in] the number of characters to return from this 
///                         SS_String, starting from the first character.
/// @return     An SS_String containing nCount characters from the beginning
///             our local buffer.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::Left(INT nCount) const
{
    ASSERT(nCount < BufferSize());
    SS_String ssResult = String();
    ssResult.SetAt(nCount, '\0');
    return ssResult;
}

// ----------------------------------------------------------------------- //
/// Returns a list of characters from the end of the local buffer.
///
/// @fn	        SS_String::Right(INT nCount) const
/// @param      nCount [in] the number of characters to return from this 
///                         SS_String, starting from the last character and
///                         counting in reverse.
/// @return     An SS_String containing nCount characters from the last 
///             character of our local buffer.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::Right(INT nCount) const
{
    ASSERT(nCount < BufferSize());
    SS_String ssResult = (LPCTSTR)(String()+Length()-nCount);
    return ssResult;
}

// ----------------------------------------------------------------------- //
/// Removes any leading white space from our local buffer.
///
/// @fn	        SS_String::TrimLeft()
/// @return     This instance of the SS_String.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::TrimLeft()
{
    LPTSTR szPos = (LPTSTR)String();
    while( !_tcsncmp(szPos, _T(" "), 1) )
        szPos++;

    if( szPos != String() )
        String((LPCTSTR)SS_String(szPos));

    return *this;
}

// ----------------------------------------------------------------------- //
/// Removes any trailing white space from our local buffer.
///
/// @fn	        SS_String::TrimRight()
/// @return     This instance of the SS_String.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::TrimRight()
{
    LPTSTR szPos = (LPTSTR)String() + Length() - 1;
    while( !_tcsncmp(szPos, _T(" "), 1) )
        szPos--;

    szPos++;
    *szPos = '\0';

    return *this;
}

// ----------------------------------------------------------------------- //
/// Replaces all instances of the chOld character with the chNew character.
///
/// @fn	        SS_String::Replace(TCHAR chOld, TCHAR chNew)
/// @param      chOld [in] character to search for for replacement
/// @param      chNew [in] - character to insert in place of chNew
/// @return     An interger count of the number of chOld characters 
///             replaced in the local string buffer.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Replace(TCHAR chOld, TCHAR chNew)
{
    INT nCountReplaced = 0;
    LPTSTR szPos = (LPTSTR)String();

    while(*szPos != '\0')
    {
        if( *szPos == chOld )
        {
            *szPos = chNew;
            nCountReplaced++;
        }

        szPos++;
    }

    return nCountReplaced;
}

// ----------------------------------------------------------------------- //
/// Replaces all instances of the szOld string with the szNew string.  If 
/// the string grows beyond the allocated size of our local buffer, we
/// will grow the buffer.  The search is case-sensitive by default, but can
/// be case-insensitive if the bIgnoreCase parameter is set to TRUE.
///
/// @fn	        SS_String::Replace(LPCTSTR szOld, LPCTSTR szNew, BOOL bIgnoreCase)
/// @param      szOld [in] string to search for for replacement
/// @param      szNew [in] string to insert in place of szOld
/// @param      bIgnoreCase [in] set to TRUE to do a case-insensitive search
///                              during the place operation
/// @return     An interger count of the number of szOld strings
///             replaced in the local string buffer.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Replace(LPCTSTR szOld, LPCTSTR szNew, BOOL bIgnoreCase)
{
    INT nCountReplaced = 0;
    INT nPos = 0;
    LPTSTR szPos = (LPTSTR)String();

    BOOL bReplace = FALSE;
    while(*szPos != '\0')
    {
        if( bIgnoreCase )
            bReplace = !_tcsnicmp(szPos, szOld, _tcslen(szOld));
        else
            bReplace = !_tcsncmp(szPos, szOld, _tcslen(szOld));

        if( bReplace )
        {
            Delete(nPos, _tcslen(szOld));
            Insert(nPos, szNew);

            // we do this in case the string got re-initialized in the Insert() function
            szPos = ((LPTSTR)String()) + nPos;

            nCountReplaced++;
            szPos += _tcslen(szNew);
            nPos += _tcslen(szNew);
        }
        else
        {
            szPos++;
            nPos++;
        }
    }

    return nCountReplaced;
}

// ----------------------------------------------------------------------- //
/// Replaces all instances of the szOld string with the szNew string.  Calling
/// this function is identical to calling the 
/// Replace(LPCTSTR szOld, LPCTSTR szNew, BOOL bIgnoreCase) function and 
/// setting the bIgnoreCase parameter to TRUE;
///
/// @fn	        SS_String::ReplaceNoCase(LPCTSTR szOld, LPCTSTR szNew)
/// @param      szOld [in] string to search for for replacement
/// @param      szNew [in] string to insert in place of szOld
/// @return     An interger count of the number of szOld strings
///             replaced in the local string buffer.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::ReplaceNoCase(LPCTSTR szOld, LPCTSTR szNew)
{
    return Replace(szOld, szNew, TRUE);
}

// ----------------------------------------------------------------------- //
/// Inserts a string at the position specified in the nPos parameter.  The 
/// local string buffer will grow if necessary.
///
/// @fn	        SS_String::Insert(INT nPos, LPCTSTR szString)
/// @param      nPos [in] zero-based position to insert the string
/// @param      szString [in] null-terminated string to insert
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Insert(INT nPos, LPCTSTR szString)
{
    ASSERT(nPos <= Length() && nPos >= 0);

    LPTSTR szThisString = GetBuffer(Length()+_tcslen(szString));
    SS_String ssString = String();

    _tcscpy(szThisString+nPos, szString);
    _tcscat(szThisString, ((LPCTSTR)(ssString))+nPos);

    ReleaseBuffer();
    return Length();
}

// ----------------------------------------------------------------------- //
/// Inserts a character at the position specified in the nPos parameter.  The 
/// local string buffer will grow if necessary.
///
/// @fn	        SS_String::Insert(INT nPos, TCHAR ch)
/// @param      nPos [in] zero-based position to insert the string
/// @param      ch [in] character to insert
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Insert(INT nPos, TCHAR ch)
{
    return Insert(nPos, &ch);
}

// ----------------------------------------------------------------------- //
/// Removes nCount characters starting from the index specified in the nPos
/// parameter.
///
/// @fn	        SS_String::Delete(INT nPos, INT nCount)
/// @param      nPos [in] zero-based position to insert the string
/// @param      nCount [in] number of characters to delete
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Delete(INT nPos, INT nCount)
{
    ASSERT(nPos <= Length() && nPos >= 0);
    ASSERT(nCount <= Length()-nPos);

    LPTSTR szThisString = GetBuffer(0);
    SS_String ssString = String();

    _tcscpy(szThisString+nPos, ((LPCTSTR)ssString)+nPos+nCount);

    ReleaseBuffer();
    return Length();
}

// ----------------------------------------------------------------------- //
/// Removes the first occurance of szString found, starting from the beginning
/// of our buffer.  To remove multiple occurances, call this function once 
/// for each removal.
///
/// @fn	        SS_String::Delete(LPCTSTR szString, BOOL bIgnoreCase)
/// @param      szString [in] null-terminated string to delete.
/// @param      bIgnoreCase [in] set to TRUE to perform a case-insensitive
///                              search during the delete operation.  Defaults
///                              to FALSE (case-sensitive).
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Delete(LPCTSTR szString, BOOL bIgnoreCase /*=FALSE*/)
{
    INT nPos;
    Find(szString, 0, &nPos, bIgnoreCase);
    if( -1 != nPos )
        Delete(nPos, _tcslen(szString));

    return Length();
}

// ----------------------------------------------------------------------- //
/// Removes the first occurance of szString found, starting from the beginning
/// of our buffer.  To remove multiple occurances, call this function once 
/// for each removal.  Calling this function is identical to calling the
/// Delete(LPCTSTR szString, BOOL bIgnoreCase) function and setting the 
/// bIgnoreCase parameter to TRUE.
///
/// @fn	        SS_String::DeleteNoCase(LPCTSTR szString)
/// @param      szString [in] null-terminated string to delete.
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::DeleteNoCase(LPCTSTR szString)
{
    return Delete(szString, TRUE);
}

// ----------------------------------------------------------------------- //
/// Removes all occurance of szString.  
///
/// @fn	        SS_String::Remove(LPCTSTR szString, BOOL bIgnoreCase)
/// @param      szString [in] null-terminated string to remove.
/// @param      bIgnoreCase [in] set to TRUE to perform a case-insensitive
///                              search during the remove operation.  Defaults
///                              to FALSE (case-sensitive).
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Remove(LPCTSTR szString, BOOL bIgnoreCase)
{
    return Replace(szString, _T(""), bIgnoreCase);
}

// ----------------------------------------------------------------------- //
/// Removes all occurance of the character supplied in the ch parameter.  
///
/// @fn	        SS_String::Remove(TCHAR ch)
/// @param      ch [in] character to remove.
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::Remove(TCHAR ch)
{
    SS_String ssCh;
    ssCh.SetAt(0, ch);
    ssCh.SetAt(1, '\0');
    return Replace(ssCh, _T(""));
}

// ----------------------------------------------------------------------- //
/// Removes all occurance of szString.  Calling this function is identical to 
/// calling the Remove(LPCTSTR szString, BOOL bIgnoreCase) function and 
/// setting the bIgnoreCase parameter to TRUE.
///
/// @fn	        SS_String::RemoveNoCase(LPCTSTR szString)
/// @param      szString [in] null-terminated string to remove.
/// @return     The string length of the resulting string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::RemoveNoCase(LPCTSTR szString)
{
    return Remove(szString, TRUE);
}

// ----------------------------------------------------------------------- //
/// This function is primarily useful for debugging purposes.  You can quickly
/// display the error text associated with Windows API functions retreived
/// from the Windows GetLastError and FormatMessage commands by calling:
/// 
///		SS_String::GetLastError();
///
/// For end-user message boxes, though,  you should probably create your own 
/// error messages for display.  
///
/// @fn	        SS_String::GetLastError(BOOL bShowMessageBox, HWND pParent, 
///                                     BOOL bShowMessageBoxIfNoError)
/// @param      bShowMessageBox [in] if TRUE, specifies that a message box
///                  should be displayed containing the last error message.
/// @param      pParent [in] parent window of the message box.  This parameter
///                  is ignored if bShowMessageBox is FALSE.
/// @param      bShowMessageBoxIfNoError [in] if FALSE, a message box will 
///                  *not* be displayed if the error is ERROR_SUCCESS (i.e. 0).
///                  This parameter is ignored if bShowMessageBox is FALSE.
/// @return     This instance of the SS_String containing the formatted
///				error message.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::GetLastError(BOOL bShowMessageBox,
                                   HWND pParent,
                                   BOOL bShowMessageBoxIfNoError)
{
    LPTSTR lpMsgBuf;
    DWORD dwResult = ::GetLastError();
    ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                     FORMAT_MESSAGE_FROM_SYSTEM |
                     FORMAT_MESSAGE_IGNORE_INSERTS,
                     NULL,
                     dwResult,
                     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                     (LPTSTR) &lpMsgBuf,
                     0,
                     NULL );

    SS_String sError = lpMsgBuf;

    if( bShowMessageBox )
    {
        if( ERROR_SUCCESS == dwResult )
        {
            if(bShowMessageBoxIfNoError)
                sError.MessageBox( pParent, _T("::GetLastError()"), MB_OK|MB_ICONINFORMATION );
        }
        else
            sError.MessageBox( pParent, _T("::GetLastError()"), MB_OK|MB_ICONINFORMATION );
    }

    // Free the buffer.
    LocalFree( lpMsgBuf );

    return sError;
}

// ----------------------------------------------------------------------- //
/// This is an sprintf style function, taking a variable number of parameters
/// and formatting them into the local buffer based on the first parameter's
/// format string.
///
/// @fn	        SS_String::Format(LPCTSTR szString, ...)
/// @param      szString [in] null-terminated sprintf style format string.  
///							  This function will take a variable number of
///							  parameters, and all parameters following this 
///							  first one will be formatted into the format
///							  string in sprintf style.
/// @return     This instance of the SS_String containing the formatted
///				string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::Format(LPCTSTR szString, ...)
{
    va_list args;
    va_start(args, szString);

    INT i = 1;
    INT nResult = 0;

    LPTSTR szBuffer = GetBuffer(SS_STR_UNIT_LEN);

    nResult = _vsntprintf(szBuffer, BufferSize(), szString, args);

    while( -1 == nResult )
    {
        ReleaseBuffer();
        i++;
        szBuffer = GetBuffer(SS_STR_UNIT_LEN*i);

		nResult = _vsntprintf(szBuffer, BufferSize(), szString, args);
    }

    ReleaseBuffer();

    va_end(args);

    return *this;
}

// ----------------------------------------------------------------------- //
/// Reads in an ASCII text file into the local buffer.  There is currently no
/// provision to avoid loading excessively large files, which would do 
/// numerous allocations and re-allocations of our local buffer.  So be 
/// careful about the files you expect this function to load.  There is
/// also no provision for asynchronous loads.  These features may be supported
/// in the future.
///
/// @fn	        SS_String::ReadFile(LPCTSTR szFileName, DWORD dwShareMode,
///								    LPSECURITY_ATTRIBUTES psa, 
///								    DWORD dwCreationDisposition,
///									DWORD dwFlagsAndAttributes, 
///								    HANDLE hTemplateFile)
/// @param      szFileName [in] filename and path to the ACSII text-based file
///                 that will be loaded into the local buffer.
/// @param		dwShareMode [in] specifies the access that other applicaations
///					can gain on the file while the SS_String has it open.  
///					Valid values are: 0, GENERIC_READ, and GENERIC_WRITE.
/// @param		psa [in] pointer to a SECURITY_ATTRIBUTES structure.  See
///					the MSDN CreateFile function for details of this 
///					structure.
///	@param		dwCreationDisposition [in] specifies instructions on 
///					what to do with existing or non-existing files.  Valid
///					values include CREATE_NEW, CREATE_ALWAYS,
///                 OPEN_EXISTING, OPEN_ALWAYS, and TRUNCATE_EXISTING.  See
///					the MSDN CreateFile function for details.
/// @param		dwFlagsAndAttributes [in] FILE_ATTRIBUTE_NORMAL, etc.  See
///					the MSDN CreateFile function for details.
/// @param		hTemplateFile [in] a handle with GENERIC_READ access to
///                 a template file, or NULL if no template file is to be
///					used.  See the MSDN CreateFile function for details.
/// @return     A DWORD value retrieved from the Windows API GetLastError
///				function after a call to either CreateFile or ReadFile.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
DWORD SS_String::ReadFile(LPCTSTR szFileName, DWORD dwShareMode,
                          LPSECURITY_ATTRIBUTES psa, DWORD dwCreationDisposition,
                          DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
    DWORD dwr = ERROR_SUCCESS;

    // check the parameters
    ASSERT(szFileName);
    if( szFileName == NULL )
        return ERROR_INVALID_PARAMETER;

    if( dwr == ERROR_SUCCESS )
    {
        // open the file
        HANDLE hFile = ::CreateFile( szFileName, GENERIC_READ, dwShareMode,
                                     psa, dwCreationDisposition, dwFlagsAndAttributes,
                                     hTemplateFile );
        dwr = ::GetLastError();
		SS_String sTmp;
		*this = sTmp;

        if( hFile != INVALID_HANDLE_VALUE )
        {
            INT i = 0;
            DWORD dwBytesRead = SS_STR_UNIT_LEN-1;
            while( dwBytesRead == SS_STR_UNIT_LEN-1 && dwr == ERROR_SUCCESS )
            {
                i++;
				sTmp = _T("");
                BOOL br = ::ReadFile( hFile, sTmp.GetBuffer(SS_STR_UNIT_LEN),
									  SS_STR_UNIT_LEN-1, &dwBytesRead, NULL );
				dwr = ::GetLastError();
				sTmp.SetAt(dwBytesRead, '\0');
                sTmp.ReleaseBuffer();
                if( br == FALSE )
                    break;

				if( dwr != ERROR_HANDLE_EOF )
					*this += sTmp;				
            }

            ::CloseHandle(hFile);
        }
    }

    return dwr;
}

// ----------------------------------------------------------------------- //
/// Writes an ASCII text file from the local buffer.  There is currently
/// no provision for asynchronous writes.  These features may be supported
/// in the future.
///
/// @fn	        SS_String::WriteFile(LPCTSTR szFileName, DWORD dwShareMode,
///								     LPSECURITY_ATTRIBUTES psa, 
///								     DWORD dwCreationDisposition,
///									 DWORD dwFlagsAndAttributes, 
///								     HANDLE hTemplateFile)
/// @param      szFileName [in] filename and path to the ACSII text-based file
///                 that will be written from the local buffer.
/// @param		dwShareMode [in] specifies the access that other applicaations
///					can gain on the file while the SS_String has it open.  
///					Valid values are: 0, GENERIC_READ, and GENERIC_WRITE.
/// @param		psa [in] pointer to a SECURITY_ATTRIBUTES structure.  See
///					the MSDN CreateFile function for details of this 
///					structure.
///	@param		dwCreationDisposition [in] specifies instructions on 
///					what to do with existing or non-existing files.  Valid
///					values include CREATE_NEW, CREATE_ALWAYS,
///                 OPEN_EXISTING, OPEN_ALWAYS, and TRUNCATE_EXISTING.  See
///					the MSDN CreateFile function for details.
/// @param		dwFlagsAndAttributes [in] FILE_ATTRIBUTE_NORMAL, etc.  See
///					the MSDN CreateFile function for details.
/// @param		hTemplateFile [in] a handle with GENERIC_READ access to
///                 a template file, or NULL if no template file is to be
///					used.  See the MSDN CreateFile function for details.
/// @return     A DWORD value retrieved from the Windows API GetLastError
///				function after a call to either CreateFile or WriteFile.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
DWORD SS_String::WriteFile(LPCTSTR szFileName, DWORD dwShareMode,
                           LPSECURITY_ATTRIBUTES psa, DWORD dwCreationDisposition,
                           DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
    DWORD dwr = ERROR_SUCCESS;

    // check the parameters
    ASSERT(szFileName);
    if( szFileName == NULL )
        return ERROR_INVALID_PARAMETER;

    if( dwr == ERROR_SUCCESS )
    {
        // open the file
        HANDLE hFile = ::CreateFile( szFileName, GENERIC_WRITE, dwShareMode,
            psa, dwCreationDisposition, dwFlagsAndAttributes,
            hTemplateFile );
        dwr = ::GetLastError();

        if( hFile != INVALID_HANDLE_VALUE )
        {
            DWORD dwBytesWritten = 0;
            ::WriteFile(hFile, String(), Length(), &dwBytesWritten, NULL);
            dwr = ::GetLastError();

            ::CloseHandle(hFile);
        }
    }

    return dwr;
}

// ----------------------------------------------------------------------- //
///	Provides a quick way to display a string resource ID in a message box.
/// Simply call:
///
///		SS_String::MessageBox(hInstance, nResourceID);
///
/// @fn	        SS_String::MessageBox(HINSTANCE hInstance,
///									  UINT nResourceID,
///									  HWND hWnd,
///									  LPCTSTR szTitle,
///									  UINT uType)
/// @param		hInstance [in] instance handle of the current process.
/// @param		nResourceID [in] a string resource ID containing the 
///					string that will be the message in the message box.
/// @param		hWnd [in] handle to the parent window.  Defaults to NULL.
/// @param		szTitle [in] null-terminated string containing text for
///					the title of the message box.  Defaults to "Dialog".
/// @param		uType [in] messagebox types, such as MB_OK or MB_YESNO. 
///					Defaults to MB_OK|MB_ICONINFORMATION.
/// @return     The user's response when closing the message box. 
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
INT SS_String::MessageBox(HINSTANCE hInstance,
						  UINT nResourceID,
					 	  HWND hWnd,
						  LPCTSTR szTitle,
						  UINT uType )
{
	ASSERT(hInstance);
	SS_String sTmp(hInstance, nResourceID);

	return sTmp.MessageBox(hWnd, szTitle, uType);
}

// ----------------------------------------------------------------------- //
///	This function releases our string buffer, leaving it up
//  to the user of the class to delete the buffer.  This
//  class will no longer maintain responsibility for 
//  for the buffer.  We will, however, reinitialize
//  another memory block and copy the string into that
//  memory block so that our string instance keeps
//  its original value.  Be sure to the pointer returned by 
//  this function as follows:
//
//      delete [] pReturned;
///
/// @fn	        SS_String::DetachBuffer()
/// @return     The pointer to the local string buffer.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::DetachBuffer()
{
    LPTSTR szReturn = GetBuffer();
    ReleaseBuffer();
    InitObject();
    String(szReturn);
    return szReturn;
}

// ----------------------------------------------------------------------- //
///	This operator is taken from the InstallShield scripting language.  It 
/// will concatenate two directory paths together, making sure that there is 
/// only 1 backslash between them.  Makes for easy assembling of directory
/// strings.  Note that we will add only backslashes.  If forward-slash paths 
/// are desired, call myString.Replace("\\", "/") after this function.  Note
/// that neither of the original two strings are altered during this call.
///
/// @fn	        SS_String::operator ^ (const SS_String& rhs)
/// @param		rhs [in] an SS_String containing a "path" to concatenate.
/// @return     An SS_String instance containing the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::operator ^ (const SS_String& rhs)
{
    SS_String sResult;
    LPCTSTR szRhs = (LPCTSTR)rhs;
    sResult = (*this) ^ szRhs;
    return sResult;
}

// ----------------------------------------------------------------------- //
///	This operator is taken from the InstallShield scripting language.  It 
/// will concatenate two directory paths together, making sure that there is 
/// only 1 backslash between them.  Makes for easy assembling of directory
/// strings.  Note that we will add only backslashes.  If forward-slash paths 
/// are desired, call myString.Replace("\\", "/") after this function.  Note
/// that neither of the original two strings are altered during this call.
///
/// @fn	        SS_String::operator ^ (LPCTSTR rhs)
/// @param		rhs [in] null-terminated "path" to concatenate.
/// @return     An SS_String instance containing the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String SS_String::operator ^ (LPCTSTR rhs)
{
    SS_String sLhs = *this;
    TCHAR chEnd = sLhs[sLhs.Length()-1];
    if( chEnd != '\\' && chEnd != '/' )
        sLhs += _T("\\");
    
    SS_String sRhs = rhs;
    if( sRhs[0] == '\\' || sRhs[0] == '/' )
        sRhs = (sRhs.GetBuffer()) + sizeof(TCHAR);
    
    SS_String sResult = (sLhs + sRhs);
    chEnd = sResult[sResult.Length()-1];
    if( chEnd == '\\' || chEnd == '/' )
        sResult.SetAt(sResult.Length()-1, '\0');
    
    return sResult;
}

// ----------------------------------------------------------------------- //
///	This operator is taken from the InstallShield scripting language.  It 
/// will concatenate two directory paths together, making sure that there is 
/// only 1 backslash between them.  Makes for easy assembling of directory
/// strings.  Note that we will add only backslashes.  If forward-slash paths 
/// are desired, call myString.Replace("\\", "/") after this function.
///
/// @fn	        SS_String::operator ^= (LPCTSTR rhs)
/// @param		rhs [in] an SS_String containing a "path" to concatenate.
/// @return     This SS_String instance containing the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::operator ^= (LPCTSTR rhs)
{
	String( (LPCTSTR)((*this) ^ rhs) );
    return *this;
}

// ----------------------------------------------------------------------- //
///	This operator is taken from the InstallShield scripting language.  It 
/// will concatenate two directory paths together, making sure that there is 
/// only 1 backslash between them.  Makes for easy assembling of directory
/// strings.  Note that we will add only backslashes.  If forward-slash paths 
/// are desired, call myString.Replace("\\", "/") after this function.
///
/// @fn	        SS_String::operator ^= (const SS_String& rhs)
/// @param		rhs [in] null-terminated "path" to concatenate.
/// @return     This SS_String instance containing the concatenated result.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
SS_String& SS_String::operator ^= (const SS_String& rhs)
{
    *this ^= rhs.String();
    return *this;
}

/// 
// ----------------------------------------------------------------------- //
///	The _ss_tcstok() function was written to replace the _tcstok function
/// because the _tcstok() function skips leading and trailing tokens, but we
/// think that leading and trailing tokens are important, and are indeed
/// delimiters signifying blank tokens.  See the MSDN strtok function for
/// details, as this function's behavior mimics that function aside from 
/// the above mentioned differences.
///
/// @fn	        SS_String::_ss_tcstok(LPTSTR szTokens, LPCTSTR szDelims)
/// @param		szTokens [in] the string containing the tokens that will
///					be parsed.
///	@param		szDelims [in] a list of delimiters.
/// @return     The next token in the list, as a null-terminated string.
/// @author     Steve Schaneville
// ----------------------------------------------------------------------- //
LPTSTR SS_String::_ss_tcstok(LPTSTR szTokens, LPCTSTR szDelims)
{
    if( !szTokens )
        szTokens = m_sz_ss_tcstok_pos;
	
    ASSERT(szTokens && szDelims);
    LPTSTR szCurrentDelim = (LPTSTR)szDelims - 1;
    LPTSTR szTokensPos = szTokens - 1;
    while( *(++szTokensPos) != '\0' )
    {
        while( *(++szCurrentDelim) != '\0' )
        {
            if( *szTokensPos == *szCurrentDelim )
            {
                // prep for next call...
                // check to make sure that we didn't have a trailing delimiter
                szCurrentDelim = (LPTSTR)szDelims - 1;
                while( *(++szCurrentDelim) != '\0' )
                {
                    TCHAR szSingleDelim[SS_STR_UNIT_LEN];
                    _tcsncpy( szSingleDelim, szCurrentDelim, 1 );
                    _tcscpy( szSingleDelim+1, _T("\0"));
                    if( !_tcscmp(szTokensPos, szSingleDelim) )
                        m_b_ss_tcstok_trailingToken = TRUE;
                }
				
                // now place '\0' and return our token
                *szTokensPos = '\0';
                m_sz_ss_tcstok_pos = szTokensPos+1;
                return szTokens;
            }
        }
        szCurrentDelim = (LPTSTR)szDelims - 1;
    }
	
    // check for trailing tokens
    if( szTokensPos != szTokens )
    {
        m_sz_ss_tcstok_pos = szTokensPos;
        return szTokens;
    }
	
    if( m_b_ss_tcstok_trailingToken )
    {
        m_b_ss_tcstok_trailingToken = FALSE;
        return szTokens;
    }
	
    return NULL;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Amedisys
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions