// TestITN.cpp : Implementation of CTestITN
#include "stdafx.h"
#include <winnls.h>
#include "Itngram.h"
#include "TestITN.h"
#include "sphelper.h"
#include "spddkhlp.h"
#include "test.h"

#define MILLION         ((LONGLONG) 1000000)
#define BILLION         ((LONGLONG) 1000000000)
#define MILLION_STR     (L"million")
#define BILLION_STR     (L"billion")

#define DAYOFWEEK_STR_ABBR  (L"ddd")
#define DAYOFWEEK_STR       (L"dddd")

#define NUM_US_STATES   57
#define NUM_CAN_PROVINCES 10

BOOL CALLBACK EnumCalendarInfoProc( LPTSTR lpCalendarInfoString )
{
    return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// CTestITN
/****************************************************************************
* CTestITN::InitGrammar *
*-----------------------*
*   Description:
*       Initialize a grammar that is loaded from an object (DLL).
*           - pszGrammarName        name of the grammar to be loaded (in case this
*                                   object supports multiple grammars)
*           - pvGrammarData         pointer to the serialized binary grammar
*   Returns:
*       S_OK
*       failure codes that are implementation specific
********************************************************************* RAL ***/

STDMETHODIMP CTestITN::InitGrammar(const WCHAR * pszGrammarName, const void ** pvGrammarData)
{
    HRESULT hr = S_OK;
    HRSRC hResInfo = ::FindResource(_Module.GetModuleInstance(), _T("TEST"), _T("ITNGRAMMAR"));
    if (hResInfo)
    {
        HGLOBAL hData = ::LoadResource(_Module.GetModuleInstance(), hResInfo);
        if (hData)
        {
            *pvGrammarData = ::LockResource(hData);
            if (*pvGrammarData == NULL)
            {
                hr = HRESULT_FROM_WIN32(::GetLastError());
            }
        }
        else
        {
            hr = HRESULT_FROM_WIN32(::GetLastError());
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(::GetLastError());
    }

    return hr;
}

/****************************************************************************
* CTestITN::Interpret *
*---------------------*
*   Description:
*       Given a phrase structure of the sub-tree spanned by this rule, given by
*       ulFristElement and ulCountOfElements and rule info (in pPhrase), examine
*       and generate new properties/text replacements which are set in the outer 
*       phrase using pSite.
*   Returns:
*       S_OK
*       S_FALSE     -- nothing was added/changed
********************************************************************* RAL ***/

STDMETHODIMP CTestITN::Interpret(ISpPhraseBuilder * pPhrase, 
                                 const ULONG ulFirstElement, 
                                 const ULONG ulCountOfElements, 
                                 ISpCFGInterpreterSite * pSite)
{
    HRESULT hr = S_OK;
    ULONG ulRuleId = 0;
    CSpPhrasePtr cpPhrase;
    
    hr = pPhrase->GetPhrase(&cpPhrase);

    m_pSite = pSite;

    // Get the minimum and maximum positions
    ULONG ulMinPos;
    ULONG ulMaxPos;
    //Just use ulFirstElement & ulCountOfElements
    //GetMinAndMaxPos( cpPhrase->pProperties, &ulMinPos, &ulMaxPos );
    ulMinPos = ulFirstElement;
    ulMaxPos = ulMinPos + ulCountOfElements; 

    // Unless otherwise specified, this is the display attributes
    BYTE bDisplayAttribs = SPAF_ONE_TRAILING_SPACE;

    if (SUCCEEDED(hr))
    {
        hr = E_FAIL;

        WCHAR pszValueBuff[ MAX_PATH ]; // No ITN result should be longer than this
        DOUBLE dblValue;                // All ITN results will have a 64-bit value

        pszValueBuff[0] = 0;

        switch (cpPhrase->Rule.ulId)
        {
        case GRID_INTEGER_STANDALONE: // Fired as a toplevel rule
            hr = InterpretNumber( cpPhrase->pProperties, true,
                &dblValue, pszValueBuff, MAX_PATH, true );

            if (SUCCEEDED( hr ) 
                && ( dblValue >= 0 ) 
                && ( dblValue <= 20 )                 
                && ( GRID_DIGIT_NUMBER != cpPhrase->pProperties->ulId )
                && ( GRID_INTEGER_MILLBILL != cpPhrase->pProperties->ulId ))
            {
                // Throw this one out because numbers like "three"
                // shouldn't be ITNed by themselves
                hr = E_FAIL;
            }
            break;
        
        case GRID_INTEGER:  case GRID_INTEGER_NONNEG:
        case GRID_INTEGER_99: case GRID_INTEGER_999: 
        case GRID_ORDINAL: 
        case GRID_MINSEC: case GRID_CLOCK_MINSEC: // Number
            hr = InterpretNumber( cpPhrase->pProperties, true, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        // Number "spelled out" digit by digit
        case GRID_DIGIT_NUMBER: case GRID_YEAR: case GRID_CENTS:
            hr = InterpretDigitNumber( cpPhrase->pProperties, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_FP_NUMBER: case GRID_FP_NUMBER_NONNEG:
            hr = InterpretFPNumber( cpPhrase->pProperties, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_INTEGER_MILLBILL:
            hr = InterpretMillBill( cpPhrase->pProperties, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_DENOMINATOR: case GRID_DENOMINATOR_SINGULAR:
            hr = InterpretNumber( cpPhrase->pProperties, true, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_FRACTION:
            hr = InterpretFraction( cpPhrase->pProperties, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_DATE:
            hr = InterpretDate( cpPhrase->pProperties, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_TIME:
            hr = InterpretTime( cpPhrase->pProperties,
                &dblValue, pszValueBuff, MAX_PATH );
            break;
        
        case GRID_STATEZIP:
            hr = InterpretStateZip( cpPhrase->pProperties,
                pszValueBuff, MAX_PATH, &bDisplayAttribs );
            break;

        case GRID_ZIPCODE: case GRID_ZIP_PLUS_FOUR:
            hr = (MakeDigitString( cpPhrase->pProperties,
                pszValueBuff, MAX_PATH ) > 0) ? S_OK : E_FAIL;
            break;

        case GRID_CAN_ZIPCODE: 
            hr = InterpretCanadaZip( cpPhrase->pProperties,
                pszValueBuff, MAX_PATH );
            break;

        case GRID_PHONE_NUMBER:
            hr = InterpretPhoneNumber( cpPhrase->pProperties,
                pszValueBuff, MAX_PATH );
            break;

        case GRID_DEGREES:
            hr = InterpretDegrees( cpPhrase->pProperties,
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_MEASUREMENT:
            hr = InterpretMeasurement( cpPhrase->pProperties,
                &dblValue, pszValueBuff, MAX_PATH );
            break;

        case GRID_CURRENCY: // Currency
            hr = InterpretCurrency( cpPhrase->pProperties, 
                &dblValue, pszValueBuff, MAX_PATH );
            break;
        
        default:
            _ASSERT(FALSE);
            break;
        }

        if ( SUCCEEDED( hr ) )
        {
            hr = AddPropertyAndReplacement( pszValueBuff, dblValue, 
                ulMinPos, ulMaxPos, ulMinPos, ulMaxPos - ulMinPos, 
                bDisplayAttribs );

            return hr;
        }

    }
    
    // Nothing was ITNed
    return S_FALSE;
}

/***********************************************************************
* CTestITN::InterpretNumber *
*---------------------------*
*   Description:
*       Interprets a number in the range -999,999,999,999 to 
*       +999,999,999,999 and sends the properties and
*       replacements to the CFGInterpreterSite as appropriate.
*       The property will be added and the pszValue will be a string 
*       with the correct display number.
*       If fCardinal is set, makes the display a cardinal number;
*       otherwise makes it an ordinal number.
*       The number will be formatted only if it was a properly-formed
*       number (not given digit by digit).
*   Result:
*************************************************************************/
HRESULT CTestITN::InterpretNumber(const SPPHRASEPROPERTY *pProperties, 
                            const bool fCardinal,
                            DOUBLE *pdblVal,
                            WCHAR *pszVal,
                            UINT cSize,
                            const bool fFinalDisplayFmt)
{
    if ( !pdblVal || !pszVal )
    {
        return E_POINTER;
    }

    *pszVal = 0;

    LONGLONG llValue = 0;
    int iPositive = 1; 

    const SPPHRASEPROPERTY *pFirstProp = pProperties;

    // Handle negatives
    if ( NEGATIVE == pFirstProp->ulId )
    {
        // There's no such thing as a negative ordinal
        SPDBG_ASSERT( fCardinal );

        // There had better be more stuff following
        SPDBG_ASSERT( pFirstProp->pNextSibling );

        iPositive = -1;

        pFirstProp = pFirstProp->pNextSibling;
    }

    // Handle the "2.6 million" case, in which case the number
    // has already been formatted
    if ( GRID_INTEGER_MILLBILL == pFirstProp->ulId )
    {
        if ( cSize < (wcslen( pFirstProp->pszValue ) + 1) )
        {
            return E_INVALIDARG;
        }
        *pdblVal = pFirstProp->vValue.dblVal * iPositive;
        if ( iPositive < 0 )
        {
            wcscpy( pszVal, m_pwszNeg );
        }
        wcscat( pszVal, pFirstProp->pszValue );

        return S_OK;
    }

    // Handle the digit-by-digit case
    if ( GRID_DIGIT_NUMBER == pFirstProp->ulId )
    {
        // There had better be nothing following this
        SPDBG_ASSERT( !pFirstProp->pNextSibling );
        
        SPDBG_ASSERT( VT_R8 == pFirstProp->vValue.vt );
        SPDBG_ASSERT( pFirstProp->pszValue );

        DOUBLE dblVal = pFirstProp->vValue.dblVal;
        *pdblVal = dblVal * iPositive;
        
        // Just get the string and make it negative if necessary
        if ( cSize < wcslen( pFirstProp->pszValue ) )
        {
            return E_INVALIDARG;
        }
        wcscpy( pszVal, pFirstProp->pszValue );
        if ( iPositive < 0 )
        {
            MakeNumberNegative( pszVal );
        }

        return S_OK;
    }

    for (const SPPHRASEPROPERTY * pProp = pFirstProp; pProp; pProp ? pProp = pProp->pNextSibling : NULL)
    {
        switch(pProp->ulId)
        {
        case ONES:
            {
                SPDBG_ASSERT(pProp->pFirstChild);
                llValue += ComputeNum999( pProp->pFirstChild );
            }
            break;
        case THOUSANDS:
            {
                llValue += ComputeNum999( pProp->pFirstChild ) * 1000;
            }
            break;
        case MILLIONS:
            {
                SPDBG_ASSERT(pProp->pFirstChild);
                llValue += ComputeNum999( pProp->pFirstChild ) * (LONGLONG) 1e6;
            }
            break;
        case BILLIONS:
            {
                SPDBG_ASSERT(pProp->pFirstChild);
                llValue += ComputeNum999( pProp->pFirstChild ) * (LONGLONG) 1e9;
            }
            break;
        case HUNDREDS:
            {
                SPDBG_ASSERT( pProp->pFirstChild );
                llValue += ComputeNum999( pProp->pFirstChild ) * 100;
            }
            break;

        case TENS:
        default:
            SPDBG_ASSERT(false);
        }
    }

    llValue *= iPositive;

    *pdblVal = (DOUBLE) llValue;

    DWORD dwDisplayFlags = DF_WHOLENUMBER 
                            | (fCardinal ? 0 : DF_ORDINAL)
                            | (fFinalDisplayFmt ? DF_MILLIONBILLION : 0 );
    return MakeDisplayNumber( *pdblVal, dwDisplayFlags, 0, 0, pszVal, cSize );

}   /* CTestITN::InterpretNumber */




/***********************************************************************
* CTestITN::InterpretDigitNumber *
*--------------------------------*
*   Description:
*       Interprets an integer in (-INF, +INF) that has been spelled
*       out digit by digit.
*       Also handles years.
*   Result:
*************************************************************************/
HRESULT CTestITN::InterpretDigitNumber( const SPPHRASEPROPERTY *pProperties,
                                        DOUBLE *pdblVal,
                                        WCHAR *pszVal,
                                        UINT cSize)
{
    if ( !pdblVal || !pszVal )
    {
        return E_POINTER;
    }

    BOOL  fPositive = TRUE;
    ULONG ulLength = 0;
    *pszVal = 0;
    WCHAR *pwc = pszVal;
    UINT cLen = 0;
    for (const SPPHRASEPROPERTY * pProp = pProperties; 
        pProp && (cLen < cSize); 
        pProp ? pProp = pProp->pNextSibling : NULL)
    {
        switch(pProp->ulId)
        {
            /*
        case NEGATIVE:
            {
                SPDBG_ASSERT( pProp == pProperties );

                fPositive = FALSE;
                *pwc++ = L'-';
                cLen++;
                break;
            }
            */
        case DIGIT:
            {
                *pwc++ = pProp->pszValue[0];
                cLen++;
                break;
            }
        case TWODIGIT:
            {
                SPDBG_ASSERT( pProp->pFirstChild );
                
                ULONG ulTwoDigit = ComputeNum999( pProp->pFirstChild );
                SPDBG_ASSERT( ulTwoDigit < 100 );

                // Get each digit
                *pwc++ = L'0' + ((UINT) ulTwoDigit) / 10;
                *pwc++ = L'0' + ((UINT) ulTwoDigit) % 10;

                cLen += 2;

                break;
            }
        case ONEDIGIT:
            {
                SPDBG_ASSERT( pProp->pFirstChild);
                
                *pwc++ = pProp->pFirstChild->pszValue[0];
                cLen++;

                break;
            }
        case TWOTHOUSAND:
            {
                // Handles the "two thousand" in dates
                if ( pProp->pNextSibling )
                {
                    if ( TWODIGIT == pProp->pNextSibling->ulId )
                    {
                        wcscpy( pwc, L"20" );
                        pwc += 2;
                        cLen += 2;
                    }
                    else
                    {
                        SPDBG_ASSERT( ONEDIGIT == pProp->pNextSibling->ulId );
                        wcscpy( pwc, L"200" );
                        pwc += 3;
                        cLen += 3;
                    }
                }
                else
                {
                    wcscpy( pwc, L"2000" );
                    pwc += 4;
                    cLen += 4;
                }
                break;
            }
        case DATE_HUNDREDS:
            {
                SPDBG_ASSERT( pProp->pFirstChild );
                DOUBLE dblTwoDigit;
                HRESULT hr = InterpretDigitNumber( pProp->pFirstChild, &dblTwoDigit, pwc, cSize - cLen );
                if ( FAILED( hr ) )
                {
                    return hr;
                }

                pwc += 2;
                *pwc++ = L'0';
                *pwc++ = L'0';
                cLen += 4;

                break;
            }


        default:
            SPDBG_ASSERT(false);
        }
    }
    *pwc = 0;

    if ( cLen <= MAX_SIG_FIGS )
    {
        *pdblVal = (DOUBLE) _wtoi64( pszVal );
    }
    else
    {
        // Just make sure it's not zero so denominators don't fail
        *pdblVal = 1;
    }

    return S_OK;
}   /* CTestITN::InterpretDigitNumber */

/***********************************************************************
* CTestITN::InterpretFPNumber *
*-----------------------------*
*   Description:
*       Interprets a floating-point number of arbitrarily many
*       sig figs.  (The value in *pdblVal will be truncated
*       as necessary to fit into a DOUBLE.)
*       The properties will look like an optional NEGATIVE property
*       followed by an optional ONES property, 
*       which has already been appropriately ITNed,
*       followed by the stuff to the right of the decimal place
************************************************************************/
HRESULT CTestITN::InterpretFPNumber( const SPPHRASEPROPERTY *pProperties,
                                    DOUBLE *pdblVal,
                                    WCHAR *pszVal,
                                    UINT cSize )
{
    if ( !pdblVal || !pszVal )
    {
        return E_POINTER;
    }
    SPDBG_ASSERT( pProperties );

    *pdblVal = 0;
    *pszVal = 0;

    
    bool fNonNegative = true;
    const SPPHRASEPROPERTY *pPropertiesFpPart = NULL;
    const SPPHRASEPROPERTY *pPropertiesPointZero = NULL;
    const SPPHRASEPROPERTY *pPropertiesOnes = NULL;
    const SPPHRASEPROPERTY *pPropertiesZero = NULL;
    const SPPHRASEPROPERTY *pPropertiesNegative = NULL;
    const SPPHRASEPROPERTY *pPropertiesPtr;

    for(pPropertiesPtr=pProperties; pPropertiesPtr; pPropertiesPtr=pPropertiesPtr->pNextSibling)
    {
        if (POINT_ZERO == pPropertiesPtr->ulId )
            pPropertiesPointZero = pPropertiesPtr;
        else if ( FP_PART == pPropertiesPtr->ulId )
            pPropertiesFpPart = pPropertiesPtr;
        else if (ONES == pPropertiesPtr->ulId )
            pPropertiesOnes = pPropertiesPtr;
        else if (ZERO == pPropertiesPtr->ulId )
            pPropertiesZero = pPropertiesPtr;
        else if (NEGATIVE == pPropertiesPtr->ulId )
            pPropertiesNegative = pPropertiesPtr;
    }

    // Get current number formatting defaults
    HRESULT hr = GetNumberFormatDefaults();
    if ( FAILED( hr ) )
    {
        return hr;
    }

    // Look for negative sign
    if ( pPropertiesNegative )
    {
        fNonNegative = false;
    }

    // Look for optional ONES (optional because you can say 
    // "point five"
    if ( pPropertiesOnes )
    {
        // This property has already been ITNed correctly,
        // so just copy in the text
        if ( (cSize - wcslen( pszVal )) < (wcslen( pPropertiesOnes->pszValue ) + 1) )
        {
            return E_INVALIDARG;
        }
        wcscpy( pszVal, pPropertiesOnes->pszValue );

        // Get the value
        *pdblVal = pPropertiesOnes->vValue.dblVal;

    }
    else if (pPropertiesZero || m_nmfmtDefault.LeadingZero )
    {
        // There should be a leading zero
        wcscpy( pszVal, L"0" );
    }

    SPDBG_ASSERT(pPropertiesFpPart || pPropertiesPointZero);

    // Put in a decimal separator
    if ( (cSize - wcslen( pszVal )) < (wcslen( m_nmfmtDefault.lpDecimalSep ) + 1) )
    {
        return E_INVALIDARG;
    }
    wcscat( pszVal, m_nmfmtDefault.lpDecimalSep );

    if ( pPropertiesFpPart )
    {
        // Deal with the FP part, which will also have been ITNed correctly
        if ( (cSize - wcslen( pszVal )) < (wcslen( pPropertiesFpPart->pszValue ) + 1) )
        {
            return E_INVALIDARG;
        }
        wcscat( pszVal, pPropertiesFpPart->pszValue );
        
        // Get the correct value
        DOUBLE dblFPPart = pPropertiesFpPart->vValue.dblVal;
        for ( UINT ui=0; ui < wcslen( pPropertiesFpPart->pszValue ); ui++ )
        {
            dblFPPart /= (DOUBLE) 10;
        }
        *pdblVal += dblFPPart;
    }
    else
    {
        // "point oh": The DOUBLE is already right, just add a "0"
        if ( (cSize - wcslen( pszVal )) < 2 )
        {
            return E_INVALIDARG;
        }
        wcscat( pszVal, L"0" );
    }

    // Handle the negative sign
    if ( !fNonNegative )
    {
        *pdblVal = -*pdblVal;

        if ( (cSize = wcslen( pszVal )) < 2 )
        {
            return E_INVALIDARG;
        }
        HRESULT hr = MakeNumberNegative( pszVal );
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }

    return S_OK;
}   /* CTestITN::InterpretFPNumber */

/***********************************************************************
* CTestITN::InterpretMillBill *
*-----------------------------*
*   Description:
*       Interprets a number that needs to be displayed with 
*       the word "million" or "billion" in the display format.  
*************************************************************************/
HRESULT CTestITN::InterpretMillBill( const SPPHRASEPROPERTY *pProperties,
                                    DOUBLE *pdblVal,
                                    WCHAR *pszVal,
                                    UINT cSize )
{
    const SPPHRASEPROPERTY *pPropertiesPtr;
    if ( !pdblVal || !pszVal )
    {
        return E_POINTER;
    }
    SPDBG_ASSERT( pProperties );
    
    HRESULT hr = GetNumberFormatDefaults();
    if ( FAILED( hr ) )
    {
        return hr;
    }

    *pszVal = 0;

    // Handle optional negative sign
    bool fNonNegative = true;
    if ( NEGATIVE == pProperties->ulId )
    {
        // Always do '-', regardless of control panel option settings
        if ( cSize < 2 )
        {
            return E_INVALIDARG;
        }
        wcscpy( pszVal, m_pwszNeg );

        cSize -= 1;
        pProperties = pProperties->pNextSibling;
    }

    // Handle the number part
    SPDBG_ASSERT( pProperties );
    for( pPropertiesPtr = pProperties; pPropertiesPtr && 
            ( GRID_INTEGER_99 != pPropertiesPtr->ulId ) && 
            ( GRID_FP_NUMBER_NONNEG != pPropertiesPtr->ulId );
         pPropertiesPtr = pPropertiesPtr->pNextSibling);
    SPDBG_ASSERT(( GRID_INTEGER_99 == pPropertiesPtr->ulId ) || 
        ( GRID_FP_NUMBER_NONNEG == pPropertiesPtr->ulId ));
    *pdblVal = pPropertiesPtr->vValue.dblVal;
    if ( cSize < (wcslen( pPropertiesPtr->pszValue ) + 1) )
    {
        return E_INVALIDARG;
    }
    wcscat( pszVal, pPropertiesPtr->pszValue );
    cSize -= wcslen( pPropertiesPtr->pszValue );
    

    // Handle the "millions" part
    SPDBG_ASSERT( pProperties );
    for( pPropertiesPtr = pProperties; pPropertiesPtr && 
            ( MILLBILL != pPropertiesPtr->ulId ); 
         pPropertiesPtr = pPropertiesPtr->pNextSibling);
    SPDBG_ASSERT( MILLBILL == pPropertiesPtr->ulId );
    *pdblVal *= ( (MILLIONS == pPropertiesPtr->vValue.uiVal) ? MILLION : BILLION );
    if ( cSize < (wcslen( pPropertiesPtr->pszValue ) + 2) )
    {
        return E_INVALIDARG;
    }
    wcscat( pszVal, L" " );
    wcscat( pszVal, pPropertiesPtr->pszValue );

    if ( !fNonNegative )
    {
        *pdblVal = -*pdblVal;
    }

    return S_OK;
}   /* CTestITN::InterpretMillBill */

/***********************************************************************
* CTestITN::InterpretFraction *
*-----------------------------*
*   Description:
*       Interprets a fraction.  
*       The DENOMINATOR property should be present.
*       If the NUMERATOR property is absent, it is assumed to be 1.
*       Divides the NUMERATOR by the DENOMINATOR and sets the value
*       accordingly.
*************************************************************************/
HRESULT CTestITN::InterpretFraction( const SPPHRASEPROPERTY *pProperties,
                                        DOUBLE *pdblVal,
                                        WCHAR *pszVal,
                                        UINT cSize)
{
    if ( !pdblVal || !pszVal )
    {
        return E_POINTER;
    }
    SPDBG_ASSERT( pProperties );
    DOUBLE dblWholeValue = 0;
    DOUBLE dblFracValue = 1;

    const SPPHRASEPROPERTY *pProp = pProperties;
    *pszVal = 0;

    // Space to store whatever characters follow the digits 
    // in the numerator (like ")")
    WCHAR pszTemp[ 10 ];    // will never need this much
    pszTemp[0] = 0;

    // Whole part is optional, otherwise assumed to be 0
    if ( WHOLE == pProp->ulId )
    {
        SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
        dblWholeValue = pProp->vValue.dblVal;
        wcscpy( pszVal, pProp->pszValue );

        // Keep track of anything that follows the digits
        WCHAR *pwc;
        for ( pwc = pszVal + wcslen(pszVal) - 1; !iswdigit( *pwc ); pwc-- )
            ;
        wcscpy( pszTemp, pwc + 1 );
        *(pwc + 1) = 0;

        // Add a space between the whole part and the fractional part
        wcscat( pszVal, L" " );

        SPDBG_ASSERT( pProp->pNextSibling );
        pProp = pProp->pNextSibling;
    }

    // Numerator is optional, otherwise assumed to be 1
    if ( NUMERATOR == pProp->ulId )
    {
        SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
        dblFracValue = pProp->vValue.dblVal;
        wcscat( pszVal, pProp->pszValue );

        // Find the last digit and copy everything after it
        WCHAR *pwc;
        for ( pwc = pszVal + wcslen(pszVal) - 1; !iswdigit( *pwc ); pwc-- )
            ;
        wcscat( pszTemp, pwc + 1 );
        *(pwc + 1) = 0;

        SPDBG_ASSERT( pProp->pNextSibling );
        pProp = pProp->pNextSibling;
    }
    else if ( ZERO == pProp->ulId )
    {
        dblFracValue = 0;
        wcscat( pszVal, L"0" );

        SPDBG_ASSERT( pProp->pNextSibling );
        pProp = pProp->pNextSibling;
    }
    else
    {
        // No numerator, assume 1
        wcscat( pszVal, L"1" );
    }




    wcscat( pszVal, L"/" );

    SPDBG_ASSERT( DENOMINATOR == pProp->ulId );
    SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
    if ( 0 == pProp->vValue.dblVal )
    {
        // Will not ITN a fraction with a zero denominator
        return E_FAIL;
    }

    dblFracValue /= pProp->vValue.dblVal;
    wcscat( pszVal, pProp->pszValue );

    // In case the denominator was an ordinal, strip off the "th" at the end
    SPDBG_ASSERT( wcslen( pszVal ) );
    WCHAR *pwc = pszVal + wcslen( pszVal ) - 1;
    for ( ; (pwc >= pszVal) && !iswdigit( *pwc ); pwc-- )
        ;
    SPDBG_ASSERT( pwc > pszVal );
    *(pwc + 1) = 0;

    // Tack on the ")" or "-" from the end of the numerator
    wcscat( pszVal, pszTemp );

    *pdblVal = dblWholeValue + dblFracValue;
    
    return S_OK;
}   /* CTestITN::InterpretFraction */

/***********************************************************************
* CTestITN::InterpretDate *
*-------------------------*
*   Description:
*       Interprets a date.
*       Converts the date into a VT_DATE format, even though it
*       gets stored as a VT_R8 (both are 64-bit quantities).
*       In case the date is not a valid date ("May fortieth nineteen
*       ninety nine") will add the properties for any numbers in there
*       and return S_FALSE.
*************************************************************************/
HRESULT CTestITN::InterpretDate( const SPPHRASEPROPERTY *pProperties,
                                        DOUBLE *pdblVal,
                                        WCHAR *pszVal,
                                        UINT cSize)
{
    if ( !pdblVal || !pszVal )
    {
        return E_POINTER;
    }

    *pszVal = 0;

    // Get the date formatting string to be used right now
    WCHAR pwszLongDateFormat[ MAX_DATE_FORMAT ];
    if ( 0 == m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SLONGDATE, 
        pwszLongDateFormat, MAX_DATE_FORMAT ) )
    {
        return E_FAIL;
    }
    
    SYSTEMTIME stDate;
    memset( (void *) &stDate, 0, sizeof( stDate ) );

    // Arguments for FormatDate()
    WCHAR *pwszFormatArg = pwszLongDateFormat;
    WCHAR pwszFormat[ MAX_DATE_FORMAT ];
    *pwszFormat = 0;
    
    bool fYear = false;     // Necessary since year can be 0
    bool fClearlyIntentionalYear = false;   // "zero one"
    bool fMonthFirst = true;    // August 2000 as opposed to 2000 August
    const SPPHRASEPROPERTY *pProp;

    if (( MONTHYEAR == pProperties->ulId ) 
        || ( YEARMONTH == pProperties->ulId ))
    {
        fMonthFirst = ( MONTHYEAR == pProperties->ulId );
        
        // Look for the year and month properties underneath this one
        pProperties = pProperties->pFirstChild;
    }

    for ( pProp = pProperties; pProp; pProp = pProp->pNextSibling )
    {
        switch ( pProp->ulId )
        {
        case DAY_OF_WEEK:
            {
                SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt );
                SPDBG_ASSERT( (0 < pProp->vValue.ulVal) && (7 >= pProp->vValue.ulVal) );
                if ( (pProp->vValue.ulVal <= 0) || (pProp->vValue.ulVal > 7) )
                {
                    return E_INVALIDARG;
                }
                stDate.wDayOfWeek = (WORD) pProp->vValue.ulVal;

                // Use the full long date format
                pwszFormatArg = pwszLongDateFormat;
            }
            break;

        case DAY_OF_MONTH:
            SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt );
            SPDBG_ASSERT( (1 <= pProp->vValue.uiVal) && (31 >= pProp->vValue.uiVal) );
            stDate.wDay = pProp->vValue.uiVal;
            break;

        case MONTH:
            SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt );
            SPDBG_ASSERT( (1 <= pProp->vValue.ulVal) && (12 >= pProp->vValue.ulVal) );
            if ( (pProp->vValue.ulVal < 1) || (pProp->vValue.ulVal > 12) )
            {
                return E_INVALIDARG;
            }
            stDate.wMonth = (WORD) pProp->vValue.ulVal;
            break;

        case YEAR:
            // Year will have been ITNed already
            SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
            stDate.wYear = (WORD) pProp->vValue.dblVal;

            fYear = true;
            if (( stDate.wYear < 10 ) && ( wcslen( pProp->pszValue ) >=2 ))
            {
                // Want to make sure "June zero one" does not 
                // become June 1 below
                fClearlyIntentionalYear = true;
            }

            break;

        default:
            SPDBG_ASSERT( false );
            break;
        }
    }

    // Make sure that grammar gave us something valid
    SPDBG_ASSERT( stDate.wMonth && 
        ( stDate.wDayOfWeek ? stDate.wDay : 1 ) );

    // Ambiguity workaround: Want to make sure that June 28 is June 28 and not June, '28
    if ( fYear && !fClearlyIntentionalYear && stDate.wMonth && !stDate.wDay && 
        (stDate.wYear >= 1) && (stDate.wYear <= 31) )
    {
        fYear = false;
        stDate.wDay = stDate.wYear;
        stDate.wYear = 0;
    }

    // Deal with the possible types of input
    if ( stDate.wDay )
    {
        if ( fYear )
        {
            if ( !stDate.wDayOfWeek )
            {
                // Remove the day of week from the current date format string.
                // This format picture can be DAYOFWEEK_STR or DAYOFWEEK_STR_ABBR.
                // This is in a loop since a pathological string could have 
                // more than one instance of the day of week...
                WCHAR *pwc = NULL;
                do
                {
                    pwc = wcsstr( pwszLongDateFormat, DAYOFWEEK_STR );
                    WCHAR pwszDayOfWeekStr[ MAX_LOCALE_DATA];
                    if ( pwc )
                    {
                        wcscpy( pwszDayOfWeekStr, DAYOFWEEK_STR );
                    }
                    else if ( NULL != (pwc = wcsstr( pwszLongDateFormat, DAYOFWEEK_STR_ABBR )) )
                    {
                        wcscpy( pwszDayOfWeekStr, DAYOFWEEK_STR_ABBR );
                    }

                    if ( pwc )
                    {
                        // A day-of-week string was found

                        // Copy over everything until this character info the format string
                        wcsncpy( pwszFormat, pwszLongDateFormat, (pwc - pwszLongDateFormat) );
                        pwszFormat[pwc - pwszLongDateFormat] = 0;
            
                        // Skip over the day of week until the next symbol
                        // (the way date format strings work, this is the first
                        // alphabetical symbol
                        pwc += wcslen( pwszDayOfWeekStr );
                        while ( *pwc && (!iswalpha( *pwc ) || (L'd' == *pwc)) )
                        {
                            pwc++;
                        }

                        // Copy over everything from here on out
                        wcscat( pwszFormat, pwc );

                        pwszFormatArg = pwszFormat;

                        // Copy over so that we can find the next day-of-week string
                        wcscpy( pwszLongDateFormat, pwszFormat );
                    }

                }   while ( pwc );
            }
            else
            {
                // The user did say the day of week
                if ( !wcsstr( pwszLongDateFormat, DAYOFWEEK_STR_ABBR ) )
                {
                    // The format string does not called for the day of week 
                    // being displayed anywhere
                    // In this case our best bet is to write out the day of week at 
                    // the beginning of the output
                    wcscpy( pwszFormat, L"dddd, " );
                    wcscat( pwszFormat, pwszLongDateFormat );

                    pwszFormatArg = pwszFormat;
                }
            }
        }
        else // fYear == 0
        {
            // Just a month and a day
            const SPPHRASEPROPERTY *pWhichComesFirst = pProperties;
            if ( stDate.wDayOfWeek )
            {
                wcscpy( pwszFormat, L"dddd, " );
                pWhichComesFirst = pWhichComesFirst->pNextSibling;
            }
            if ( MONTH == pWhichComesFirst->ulId )
            {
                wcscat( pwszFormat, L"MMMM d" );
            }
            else
            {
                wcscat( pwszFormat, L"d MMMM" );
            }

            pwszFormatArg = pwszFormat;
        }
    }
    else // stDate.wDay == 0
    {
        // Month, year format
        if ( fMonthFirst )
        {
            wcscpy( pwszFormat, L"MMMM, yyyy" );
        }
        else
        {
            wcscpy( pwszFormat, L"yyyy MMMM" );
        }

        pwszFormatArg = pwszFormat;
    }

    // Get the date in VariantTime form so we can set it as a semantic value
    int iRet = ::SystemTimeToVariantTime( &stDate, pdblVal );
    if ( 0 == iRet )
    {
        // Not serious, just the semantic value will be wrong
        *pdblVal = 0;
    }

    // Do the formatting
    iRet = FormatDate( stDate, pwszFormatArg, pszVal, cSize );
    if ( 0 == iRet )
    {
        return E_FAIL;
    }


    return S_OK;
}   /* CTestITN::InterpretDate */

/***********************************************************************
* CTestITN::InterpretTime *
*--------------------------*
*   Description:
*       Interprets time, which can be of the following forms:
*       *   Hour with qualifier ("half past three"), time marker optional
*       *   Hour with minutes, time marker mandatory
*       *   Military time "hundred hours"
*       *   Hour with "o'clock", time marker optional
*       *   Number of hours and number of minutes and optional number
*           of seconds
*   Return:
*       S_OK
*       E_POINTER if !pdblVal or !pszVal
*************************************************************************/
HRESULT CTestITN::InterpretTime( const SPPHRASEPROPERTY *pProperties,
                                DOUBLE *pdblVal, 
                                WCHAR *pszVal, 
                                UINT cSize )
{
    if ( !pdblVal || !pszVal )
    {
        return E_POINTER;
    }
    // Time marker and seconds should not be shown unless some
    // component of the time specifically requires it
    DWORD dwFlags = TIME_NOSECONDS | TIME_NOTIMEMARKER;
    SYSTEMTIME stTime;
    ::memset( (void *) &stTime, 0, sizeof( SYSTEMTIME ) );

    bool fQuarterTo = false;
    bool fClockTime = true;
    bool fAMPM = false;
    UINT uAMPM = AM; 

    const SPPHRASEPROPERTY *pProp;
    for ( pProp = pProperties; pProp; pProp = pProp->pNextSibling )
    {
        switch ( pProp->ulId )
        {
        case HOUR_CLOCK:
            {
                UINT uiHour = pProp->vValue.uiVal;
                SPDBG_ASSERT(( uiHour > 0 ) && ( uiHour <= 12));
                if ( fQuarterTo )
                {
                    // Push the hour back by one 
                    // (push back to 12 if it's one)
                    uiHour = (1 == uiHour) ? 12 : (uiHour - 1);
                }

                if ( 12 == uiHour )
                {
                    // The functions below are expecting "0" to indicate midnight
                    uiHour = 0;
                }

                stTime.wHour = (WORD) uiHour;
                break;
            }
        case HOUR_COUNT:
            // Just take the hour for what it is
            stTime.wHour = (WORD) pProp->vValue.dblVal;

            // This is not a clock time
            fClockTime = false;
            break;

        case MINUTE:
            {
                // Minutes are evaluted as numbers, so their values
                // are stored as doubles
                stTime.wMinute = (WORD) pProp->vValue.dblVal;
                break;
            }
        case SECOND:
            {
                stTime.wSecond = (WORD) pProp->vValue.dblVal;
                dwFlags &= ~TIME_NOSECONDS;
                break;
            }
        case CLOCKTIME_QUALIFIER:
            {
                switch( pProp->vValue.uiVal )
                {
                case QUARTER_TO:
                    {
                        fQuarterTo = true;
                        stTime.wMinute = 45;
                        break;
                    }
                case QUARTER_PAST:
                    {
                        stTime.wMinute = 15;
                        break;
                    }
                case HALF_PAST:
                    {
                        stTime.wMinute = 30;
                        break;
                    }
                default:
                    SPDBG_ASSERT( false );
                }

                break;
            }
        case AMPM:
            {
                // We don't know where it might arrive any more, so simple keep this information
                fAMPM = true;
                uAMPM = pProp->vValue.uiVal;
                break;
            }
        default:
            SPDBG_ASSERT( false );
        }
    }
	    
    if (fAMPM)
    {
		
        SPDBG_ASSERT(( stTime.wHour >= 0 ) && ( stTime.wHour <= 11 ));
        if ( PM == uAMPM )
        {
            stTime.wHour += 12;
        }
        dwFlags &= ~TIME_NOTIMEMARKER;
    }

    HRESULT hr = S_OK;
    if ( fClockTime )
    {
        // Get the time in VariantTime form so we can set it as a semantic value
        if ( 0 == ::SystemTimeToVariantTime( &stTime, pdblVal ) )
        {
            // Not serious, just the semantic value will be wrong
            *pdblVal = 0;
        }

        // Let the system format the time
        int iRet = m_Unicode.GetTimeFormat( LOCALE_USER_DEFAULT, dwFlags, &stTime, NULL, 
            pszVal, cSize );

        // NB: GetTimeFormat() will put an extra space at the end of the 
        // time if the default format has AM or PM but TIME_NOTIMEMARKER is
        // set
        if ( iRet && (TIME_NOTIMEMARKER & dwFlags) )
        {
            WCHAR *pwc = pszVal + wcslen( pszVal ) - 1;
            while ( iswspace( *pwc ) )
            {
                *pwc-- = 0;
            }
        }
        hr = iRet ? S_OK : E_FAIL;
    }
    else
    {
        // No need to go through the system's time formatter
        if ( cSize < 10 )    // Space for xxx:xx:xx\0
        {
            return E_INVALIDARG;
        }

        if ( dwFlags & TIME_NOSECONDS )
        {
            swprintf( pszVal, L"%d:%02d", stTime.wHour, stTime.wMinute );
        }
        else
        {
            swprintf( pszVal, L"%d:%02d:%02d", 
                stTime.wHour, stTime.wMinute, stTime.wSecond );
        }
    }

    return hr;
}   /* CTestITN::InterpretTime */

/***********************************************************************
* CTestITN::InterpretStateZip *
*-----------------------------*
*   Description:
*       A StateZip must be a state name followed by a ZIP code.
*       There is no reasonable semantic value to attach to this ITN.        
*   Return:
*       S_OK
*       E_POINTER if !pszVal
*       E_INVALIDARG if cSize is too small
*************************************************************************/
HRESULT CTestITN::InterpretStateZip( const SPPHRASEPROPERTY *pProperties,
                                    WCHAR *pszVal,
                                    UINT cSize,
                                    BYTE *pbAttribs )
{
    if ( !pszVal || !pProperties || !pbAttribs )
    {
        return E_POINTER;
    }
    if ( cSize < MAX_STATEZIP )
    {
        return E_INVALIDARG;
    }

    const SPPHRASEPROPERTY *pPropertiesComma = NULL;
    const SPPHRASEPROPERTY *pPropertiesState = NULL;
    const SPPHRASEPROPERTY *pPropertiesZipCode = NULL;
    const SPPHRASEPROPERTY *pPropertiesZipCodeExtra = NULL;
    const SPPHRASEPROPERTY *pPropertiesPtr;

    for(pPropertiesPtr=pProperties; pPropertiesPtr; pPropertiesPtr=pPropertiesPtr->pNextSibling)
    {
        if (COMMA == pPropertiesPtr->ulId )
            pPropertiesComma = pPropertiesPtr;
        else if ( (US_STATE == pPropertiesPtr->ulId ) || (CAN_PROVINCE == pPropertiesPtr->ulId ))
            pPropertiesState = pPropertiesPtr;
        else if (ZIPCODE == pPropertiesPtr->ulId )
            pPropertiesZipCode = pPropertiesPtr;
        else if (FOURDIGITS == pPropertiesPtr->ulId )
            pPropertiesZipCodeExtra = pPropertiesPtr;
    }

    // Comma after the city name if a comma was spoken
    if ( pPropertiesComma )
    {
        // Will want to consume leading spaces when this is displayed
        *pbAttribs |= SPAF_CONSUME_LEADING_SPACES;

        wcscpy( pszVal, L", " );
    }

    // Get the state name
    SPDBG_ASSERT( pPropertiesState );
    UINT uiState = pPropertiesState->vValue.uiVal;
    if ( US_STATE == pPropertiesState->ulId )
    {
        SPDBG_ASSERT( uiState < NUM_US_STATES );
        wcscat( pszVal, pPropertiesState->pszValue );
    }
    else if ( CAN_PROVINCE == pPropertiesState->ulId )
    {
        SPDBG_ASSERT( uiState < NUM_CAN_PROVINCES );
        wcscat( pszVal, pPropertiesState->pszValue );
    }
    else
    {
        SPDBG_ASSERT( false );
    }
    
    wcscat( pszVal, L" " );

    // Get the ZIP
    SPDBG_ASSERT( pPropertiesZipCode );
    wcscat( pszVal, pPropertiesZipCode->pszValue );

    // Get the ZIP+4 if it's there
    
    if ( pPropertiesZipCodeExtra )
    {
        wcscat( pszVal, L"-" );
        wcscat( pszVal, pPropertiesZipCodeExtra->pszValue );
    }

    return S_OK;
}   /* CTestITN::InterpretStateZip */

/***********************************************************************
* CTestITN::InterpretCanadaZip *
*------------------------------*
*   Description:
*       A CanadaZip must be Alpha/Num/Alpha Num/Alpha/Num
*       There is no reasonable semantic value to attach to this ITN.        
*   Return:
*       S_OK
*       E_POINTER if !pszVal
*       E_INVALIDARG if cSize is too small
*************************************************************************/
HRESULT CTestITN::InterpretCanadaZip( const SPPHRASEPROPERTY *pProperties,
                                    WCHAR *pszVal,
                                    UINT cSize )
{
    if ( !pszVal )
    {
        return E_POINTER;
    }
    if ( cSize < CANADIAN_ZIPSIZE )
    {
        return E_INVALIDARG;
    }

    int i;
    for ( i=0; i < 3; i++, pProperties = pProperties->pNextSibling )
    {
        SPDBG_ASSERT( pProperties );
        wcscat( pszVal, pProperties->pszValue );
    }
    wcscat( pszVal, L" " );
    for ( i=0; i < 3; i++, pProperties = pProperties->pNextSibling )
    {
        SPDBG_ASSERT( pProperties );
        wcscat( pszVal, pProperties->pszValue );
    }
    return S_OK;
}   /* CTestITN::InterpretStateZip */

/***********************************************************************
* CTestITN::InterpretPhoneNumber *
*--------------------------------*
*   Description:
*       Phone number      
*   Return:
*       S_OK
*       E_POINTER if !pszVal
*       E_INVALIDARG if cSize is too small
*************************************************************************/
HRESULT CTestITN::InterpretPhoneNumber( const SPPHRASEPROPERTY *pProperties,
                                       WCHAR *pszVal,
                                       UINT cSize )
{
    if ( !pProperties || !pszVal )
    {
        return E_POINTER;
    }

    if ( cSize < MAX_PHONE_NUMBER )
    {
        return E_INVALIDARG;
    }

    pszVal[0] = 0;

    if ( ONE_PLUS == pProperties->ulId )
    {
        SPDBG_ASSERT( pProperties->pNextSibling && 
            (AREA_CODE == pProperties->pNextSibling->ulId) );
        wcscat( pszVal, L"1-" );

        pProperties = pProperties->pNextSibling;
    }

    if ( AREA_CODE == pProperties->ulId )
    {
        SPDBG_ASSERT( pProperties->pNextSibling );

        wcscat( pszVal, L"(" );
        
        SPDBG_ASSERT( pProperties->pFirstChild );
        if ( DIGIT == pProperties->pFirstChild->ulId )
        {
            // Area code spelled out digit by digit
            if ( 4 != MakeDigitString( 
                pProperties->pFirstChild, pszVal + wcslen( pszVal ), 
                cSize - wcslen( pszVal ) ) )
            {
                return E_INVALIDARG;
            }
        }
        else
        {
            // 800 or 900
            SPDBG_ASSERT( AREA_CODE == pProperties->pFirstChild->ulId );
            wcscat( pszVal, pProperties->pFirstChild->pszValue );
        }

        wcscat( pszVal, L")-" );

        pProperties = pProperties->pNextSibling;
    }

    // Exchange
    SPDBG_ASSERT( PHONENUM_EXCHANGE == pProperties->ulId );
    SPDBG_ASSERT( pProperties->pFirstChild );
    if ( 4 != MakeDigitString( 
        pProperties->pFirstChild, pszVal + wcslen( pszVal ), 
        cSize - wcslen( pszVal ) ) )
    {
        return E_INVALIDARG;
    }
    wcscat( pszVal, L"-");
    SPDBG_ASSERT( pProperties->pNextSibling );
    pProperties = pProperties->pNextSibling;

    SPDBG_ASSERT( FOURDIGITS == pProperties->ulId );
    SPDBG_ASSERT( pProperties->pFirstChild );
    if ( 5 != MakeDigitString( 
        pProperties->pFirstChild, pszVal + wcslen( pszVal ), 
        cSize - wcslen( pszVal ) ) )
    {
        return E_INVALIDARG;
    }
    pProperties = pProperties->pNextSibling;

    if ( pProperties )
    {
        // extension
        SPDBG_ASSERT( EXTENSION == pProperties->ulId );
        SPDBG_ASSERT( pProperties->pFirstChild );
        wcscat( pszVal, L"x" );
        if ( 0 == MakeDigitString( 
            pProperties->pFirstChild, pszVal + wcslen( pszVal ), 
            cSize - wcslen( pszVal ) ) )
        {
            return E_INVALIDARG;
        }
        
        pProperties = pProperties->pNextSibling;
    }

    // Make sure there's nothing else here!
    SPDBG_ASSERT( !pProperties );

    return S_OK;
}   /* CTestITN::InterpretPhoneNumber */

/***********************************************************************
* CTestITN::InterpretDegrees *
*----------------------------*
*   Description:
*       Interprets degrees as a temperature, angle-measurement,
*       or direction, as appropriate.
*   Return:
*       S_OK
*       E_POINTER
*       E_INVALIDARG
*************************************************************************/
HRESULT CTestITN::InterpretDegrees( const SPPHRASEPROPERTY *pProperties,
                                   DOUBLE *pdblVal, 
                                   WCHAR *pszVal,
                                   UINT cSize )
{
    if ( !pProperties || !pdblVal || !pszVal )
    {
        return E_POINTER;
    }

    *pszVal = 0;

    const SPPHRASEPROPERTY *pPropertiesDegree = NULL;
    const SPPHRASEPROPERTY *pPropertiesMinute = NULL;
    const SPPHRASEPROPERTY *pPropertiesSecond = NULL;
    const SPPHRASEPROPERTY *pPropertiesDirection = NULL;
    const SPPHRASEPROPERTY *pPropertiesUnit = NULL;
    const SPPHRASEPROPERTY *pPropertiesPtr;
    // Get the number

    for(pPropertiesPtr=pProperties; pPropertiesPtr; pPropertiesPtr=pPropertiesPtr->pNextSibling)
    {
        if (TEMP_UNITS == pPropertiesPtr->ulId )
            pPropertiesUnit = pPropertiesPtr;
        else if ( (GRID_INTEGER_NONNEG == pPropertiesPtr->ulId ) || (NUMBER == pPropertiesPtr->ulId ))
            pPropertiesDegree = pPropertiesPtr;
        else if (MINUTE == pPropertiesPtr->ulId )
            pPropertiesMinute = pPropertiesPtr;
        else if (SECOND == pPropertiesPtr->ulId )
            pPropertiesSecond = pPropertiesPtr;
        else if (DIRECTION == pPropertiesPtr->ulId )
            pPropertiesDirection = pPropertiesPtr;
    }

    SPDBG_ASSERT( pPropertiesDegree );
    *pdblVal = pPropertiesDegree->vValue.dblVal;
    wcscat( pszVal, pPropertiesDegree->pszValue );
    wcscat( pszVal, L"�" );  

    if ( pPropertiesUnit )
    { 
        wcscat( pszVal, pPropertiesUnit->pszValue );
    }
    if ( pPropertiesMinute || pPropertiesSecond)
    {
        SPDBG_ASSERT( *pdblVal >= 0 );
        
        if ( pPropertiesMinute )
        {
            DOUBLE dblMin = pPropertiesMinute->vValue.dblVal;
            *pdblVal += dblMin / (DOUBLE) 60;
            wcscat( pszVal, pPropertiesMinute->pszValue );
            wcscat( pszVal, L"'" );
        }

        if ( pPropertiesSecond )
        {
            DOUBLE dblSec = pPropertiesSecond->vValue.dblVal;
            *pdblVal += dblSec / (DOUBLE) 3600;
            wcscat( pszVal, pPropertiesSecond->pszValue );
            wcscat( pszVal, L"''" );
        }
    }

    if ( pPropertiesDirection )
    {
        wcscat( pszVal, pPropertiesDirection->pszValue );
    }
        


    return S_OK;
}   /* CTestITN::InterpretDegrees */

/***********************************************************************
* CTestITN::InterpretMeasurement *
*--------------------------------*
*   Description:
*       Interprets measurements, which is a number followed
*       by a units name
*   Return:
*       S_OK
*       E_POINTER
*       E_INVALIDARG
*************************************************************************/
HRESULT CTestITN::InterpretMeasurement( const SPPHRASEPROPERTY *pProperties,
                                       DOUBLE *pdblVal,
                                       WCHAR *pszVal, 
                                       UINT cSize )
{
    if ( !pProperties || !pdblVal || !pszVal )
    {
        return E_POINTER;
    }

    const SPPHRASEPROPERTY *pPropNumber = NULL;
    const SPPHRASEPROPERTY *pPropUnits = NULL;
    const SPPHRASEPROPERTY *pProp;
    for(pProp= pProperties; pProp; pProp = pProp->pNextSibling)
    {
        if (NUMBER == pProp->ulId )
            pPropNumber = pProp;
        else if ( UNITS == pProp->ulId )
            pPropUnits = pProp;
    }

    if (!pPropNumber || !pPropUnits)
    {
        SPDBG_ASSERT( FALSE );
        return E_INVALIDARG;
    }

    if ( cSize < (wcslen(pPropNumber->pszValue) + wcslen(pPropUnits->pszValue) + 1) )
    {
        // Not enough space
        return E_INVALIDARG;
    }

    wcscpy( pszVal, pPropNumber->pszValue );
    wcscat( pszVal, pPropUnits->pszValue );

    *pdblVal = pPropNumber->vValue.dblVal;

    return S_OK;
}   /* CTestITN::InterpretMeasurement */

/***********************************************************************
* CTestITN::InterpretCurrency *
*-----------------------------*
*   Description:
*       Interprets currency.
*   Return:
*       S_OK
*       E_POINTER if !pdblVal or !pszVal
*       E_INVALIDARG if the number of cents is not between 0 and 99 
*           inclusive
*************************************************************************/
HRESULT CTestITN::InterpretCurrency( const SPPHRASEPROPERTY *pProperties,
                                        DOUBLE *pdblVal,
                                        WCHAR *pszVal,
                                        UINT cSize)
{
    if ( !pdblVal || !pszVal || !pProperties )
    {
        return E_POINTER;
    }

    const SPPHRASEPROPERTY *pPropDollars = NULL;
    const SPPHRASEPROPERTY *pPropCents = NULL;
    const SPPHRASEPROPERTY *pPropType = NULL;
    const SPPHRASEPROPERTY *pPropSmallType = NULL;
    const SPPHRASEPROPERTY *pPropNegative = NULL;
    const SPPHRASEPROPERTY *pProp;
    for(pProp= pProperties; pProp; pProp = pProp->pNextSibling)
    {
        if (NEGATIVE == pProp->ulId )
            pPropNegative = pProp;
        else if ( DOLLARS == pProp->ulId )
            pPropDollars = pProp;
        else if ( CENTS == pProp->ulId )
            pPropCents = pProp;
        else if ( CURRENCY_TYPE == pProp->ulId )
            pPropType = pProp;
        else if ( CURRENCY_SMALL_TYPE == pProp->ulId )
            pPropSmallType = pProp;
    }

    *pszVal = 0;
    *pdblVal = 0;

    bool fNonNegative = true;
    if ( pPropNegative )
    {
        fNonNegative = false;
    }

    bool fUseDefaultCurrencySymbol = true;

    if ( pPropDollars )
    {
        // If "dollars" was said, override the default currency symbol
        if ( pPropType )
        {
            fUseDefaultCurrencySymbol = false;
        }
    }

    if ( pPropDollars )
    {
        // Dollars and possibly cents will be here, so we want to 
        // use regional format
        HRESULT hr = GetCurrencyFormatDefaults();
        if ( FAILED( hr ) )
        {
            return hr;
        }

        *pdblVal = pPropDollars->vValue.dblVal;

        // Handle the case of "$5 million".  If this should happen, there
        // will be some alphabetic string in the string value for the number,
        // and there will be no cents
        if ( !pPropCents )
        {
            WCHAR *pwc = wcsstr( pPropDollars->pszValue, MILLION_STR );
            if ( !pwc )
            {
                pwc = wcsstr( pPropDollars->pszValue, BILLION_STR );
            }

            if ( pwc )
            {
                // Either "million" or "billion" was in there

                // Just a dollar sign followed by the number string value
                if ( !fNonNegative )
                {
                    wcscpy( pszVal, m_pwszNeg );
                    *pdblVal = -*pdblVal;
                }
                wcscat( pszVal, pPropType->pszValue );
                wcscat( pszVal, pPropDollars->pszValue );
                return S_OK;
            }
        }

        // Use the associated currency symbol
        if ( !fUseDefaultCurrencySymbol )
        {
            wcscpy( m_pwszCurrencySym, pPropType->pszValue );
            m_cyfmtDefault.lpCurrencySymbol = m_pwszCurrencySym;
        }
        // else... use the currency symbol obtained in GetCurrencyFormatDefaults()

        if ( pPropCents )
        {
            SPDBG_ASSERT( (pPropCents->vValue.dblVal >= 0) && 
                (pPropCents->vValue.dblVal < 100) );
            DOUBLE dblCentsVal = pPropCents->vValue.dblVal / (DOUBLE) 100;
            if ( *pdblVal >= 0 )
            {
                *pdblVal += dblCentsVal;
            }
            else
            {
                *pdblVal -= dblCentsVal;
            }
        }
        else
        {
            // count up number of decimal places.
            // Need to use the original formatted number
            // in case someone explicitly gave some zeroes
            // as significant digits
            const WCHAR *pwszNum = pPropDollars->pszValue;
            WCHAR pwszNumDecimalSep[ MAX_LOCALE_DATA ];
            *pwszNumDecimalSep = 0;
            int iRet = m_Unicode.GetLocaleInfo( 
                ::GetUserDefaultLCID(), LOCALE_SDECIMAL, pwszNumDecimalSep, MAX_LOCALE_DATA );
            WCHAR *pwc = wcsstr( pwszNum, pwszNumDecimalSep );

            UINT cDigits = 0;
            if ( pwc && iRet )
            {
                for ( pwc = pwc + 1; *pwc && iswdigit( *pwc ); pwc++ )
                {
                    cDigits++;
                }
            }
            m_cyfmtDefault.NumDigits = __max( m_cyfmtDefault.NumDigits, cDigits );
        }

        // Handle the negative sign in the value
        if ( !fNonNegative )
        {
            *pdblVal = -*pdblVal;
        }
        
        // Write the unformatted number to a string
        WCHAR *pwszUnformatted = new WCHAR[ cSize ];
        if ( !pwszUnformatted )
        {
            return E_OUTOFMEMORY;
        }
        swprintf( pwszUnformatted, L"%f", *pdblVal );
    

        int iRet = m_Unicode.GetCurrencyFormat( LOCALE_USER_DEFAULT, 0, pwszUnformatted,
            &m_cyfmtDefault, pszVal, cSize );
        delete[] pwszUnformatted;

        if ( !iRet )
        {
            return E_FAIL;
        }
    }
    else
    {
        // Just cents: better have said "cents"
        SPDBG_ASSERT( pPropSmallType );

        *pdblVal = pPropCents->vValue.dblVal / (DOUBLE) 100;

        // Cents are always displayed as 5c, regardless of locale settings.

        // Copy over the formatted number
        wcscpy( pszVal, pPropCents->pszValue );

        // Add on the cents symbol
        wcscat( pszVal, pPropSmallType->pszValue );
    }

    return S_OK;
}   /* CTestITN::InterpretCurrency */

/***********************************************************************
* CTestITN::AddPropertyAndReplacement *
*-------------------------------------*
*   Description:
*       Takes all of the info that we want to pass into the 
*       engine site, forms the SPPHRASEPROPERTY and
*       SPPHRASERREPLACEMENT, and adds them to the engine site
*   Return:
*       Return values of ISpCFGInterpreterSite::AddProperty()
*           and ISpCFGInterpreterSite::AddTextReplacement()
*************************************************************************/
HRESULT CTestITN::AddPropertyAndReplacement( const WCHAR *szBuff,
                                    const DOUBLE dblValue,
                                    const ULONG ulMinPos,
                                    const ULONG ulMaxPos,
                                    const ULONG ulFirstElement,
                                    const ULONG ulCountOfElements,
                                    const BYTE bDisplayAttribs )
{
    // Add the property 
    SPPHRASEPROPERTY prop;
    memset(&prop,0,sizeof(prop));
    prop.pszValue = szBuff;
    prop.vValue.vt = VT_R8;
    prop.vValue.dblVal = dblValue;
    prop.ulFirstElement = ulMinPos;
    prop.ulCountOfElements = ulMaxPos - ulMinPos;
    HRESULT hr = m_pSite->AddProperty(&prop);

    if (SUCCEEDED(hr))
    {
        SPPHRASEREPLACEMENT repl;
        memset(&repl,0, sizeof(repl));
        repl.bDisplayAttributes = bDisplayAttribs;
        repl.pszReplacementText = szBuff;
        repl.ulFirstElement = ulFirstElement;
        repl.ulCountOfElements = ulCountOfElements;
        hr = m_pSite->AddTextReplacement(&repl);
    }

    return hr;
}   /* CTestITN::AddPropertyAndReplacement */

// Helper functions

 
/***********************************************************************
* CTestITN::MakeDisplayNumber *
*-----------------------------*
*   Description:
*       Converts a DOUBLE into a displayable
*       number in the range -999,999,999,999 to +999,999,999,999.
*       cSize is the number of chars for which pwszNum has space
*       allocated.
*       If DF_UNFORMATTED is set, all other flags are ignored,
*           and the number is passed back as an optional negative
*           followed by a string of digits
*       If DF_ORDINAL is set in dwDisplayFlags, displays an
*           ordinal number (i.e. tacks on "th" or the appropriate suffix.
*       If DF_WHOLENUMBER is set, lops off the decimal separator
*           and everything after it.  If DF_WHOLENUMBER is not set,
*           then uses the uiDecimalPlaces parameter to determine
*           how many decimal places to display
*       If DF_FIXEDWIDTH is set, will display at least uiFixedWidth
*           digits; otherwise uiFixedWidth is ignored.
*       If DF_NOTHOUSANDSGROUP is set, will not do thousands
*           grouping (commas)
*************************************************************************/
HRESULT CTestITN::MakeDisplayNumber( DOUBLE dblNum, 
                         DWORD dwDisplayFlags,
                         UINT uiFixedWidth,
                         UINT uiDecimalPlaces,
                         WCHAR *pwszNum,
                         UINT cSize )
{
    SPDBG_ASSERT( pwszNum );
    SPDBG_ASSERT( !SPIsBadWritePtr( pwszNum, cSize ) );
    *pwszNum = 0;

    // Get the default number formatting.
    // Note that this gets called every time, since otherwise there
    // is no way to pick up changes that the user has made since
    // this process has started.
    HRESULT hr = GetNumberFormatDefaults();
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    // check for straight millions and straight billions
    // NB: This is a workaround for the fact that we can't resolve the ambiguity
    // and get "two million" to go through GRID_INTEGER_MILLBILL
    if (( dwDisplayFlags & DF_WHOLENUMBER ) && ( dwDisplayFlags & DF_MILLIONBILLION ) && (dblNum > 0))
    {
        HRESULT hr;
        if ( 0 == (( ((LONGLONG) dblNum) % BILLION )) )
        {
            // e.g. for "five billion" get the "5" and then 
            // tack on " billion"
            hr = MakeDisplayNumber( ( dblNum / ((DOUBLE) BILLION) ), 
                dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pwszNum, cSize );
            if ( SUCCEEDED( hr ) )
            {
                wcscat( pwszNum, L" " );
                wcscat( pwszNum, BILLION_STR );
            }
            return hr;
        }
        else if (( ((LONGLONG) dblNum) < BILLION ) && 
                ( 0 == (( ((LONGLONG) dblNum) % MILLION )) ))
        {
            hr = MakeDisplayNumber( ( dblNum / ((DOUBLE) MILLION) ), 
                dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pwszNum, cSize );
            if ( SUCCEEDED( hr ) )
            {
                wcscat( pwszNum, L" " );
                wcscat( pwszNum, MILLION_STR );
            }
                return hr;
        }
    }

    // Put in the negative sign if necessary
    if ( dblNum < 0 )
    {
        wcscat( pwszNum, L"-" );

        // From now on we want to deal with the magnitude of the number
        dblNum *= -1;
    }
    SPDBG_ASSERT( dblNum < 1e12 );

    WCHAR *pwszTemp = new WCHAR[ cSize ];
    if ( !pwszTemp )
    {
        return E_OUTOFMEMORY;
    }
    *pwszTemp = 0;

    LONGLONG llIntPart = (LONGLONG) dblNum;
    UINT64 uiDigitsLeftOfDecimal;
    if ( dwDisplayFlags & DF_WHOLENUMBER )
    {
        swprintf( pwszTemp, L"%I64d", llIntPart );
        uiDigitsLeftOfDecimal = wcslen( pwszTemp );
    }
    else
    {
        swprintf( pwszTemp, L"%.*f", uiDecimalPlaces, dblNum );
        WCHAR *pwc = wcschr( pwszTemp, L'.' );
        uiDigitsLeftOfDecimal = pwc - pwszTemp;
    }
    
    // The following handles the case where the user said something
    // like "zero zero zero three" and wants to see "0,003"
    BOOL fChangedFirstDigit = false;
    const WCHAR wcFakeFirstDigit = L'1';
    if ( !(dwDisplayFlags & DF_UNFORMATTED) && 
        (dwDisplayFlags & DF_FIXEDWIDTH) && (uiDigitsLeftOfDecimal < uiFixedWidth) )
    {
        // The following handles the case where the user wants leading 
        // zeroes displayed
        
        // Need to pad the front with zeroes
        for ( UINT ui = 0; ui < (uiFixedWidth - uiDigitsLeftOfDecimal); ui++ )
        {
            wcscat( pwszNum, L"0" );
        }
        
        // HACK
        // In order to force something like "zero zero zero three" 
        // into the form "0,003", we need to make GetNumberFormat() 
        // think that the first digit is 1.
        WCHAR *pwc = wcschr( pwszNum, L'0' );
        SPDBG_ASSERT( pwc );
        *pwc = wcFakeFirstDigit;
        fChangedFirstDigit = true;
    }

    // Copy over the unformatted number after the possible negative sign
    wcscat( pwszNum, pwszTemp );
    delete[] pwszTemp;

    // If we do not want to format the number, then bail here
    if ( dwDisplayFlags & DF_UNFORMATTED )
    {
        return S_OK;
    }

    // Make a copy so that we can change some fields according to the 
    // flags param
    NUMBERFMTW nmfmt = m_nmfmtDefault;

    // How many decimal places to display?
    if ( dwDisplayFlags & DF_WHOLENUMBER )
    {
        nmfmt.NumDigits = 0;
    }
    else
    {
        // Use the uiDecimalPlaces value to determine how
        // many to display
        nmfmt.NumDigits = uiDecimalPlaces;
    }
    
    // Leading zeroes?
    nmfmt.LeadingZero = (dwDisplayFlags & DF_LEADINGZERO) ? 1 : 0;

    // Thousands grouping?
    if ( dwDisplayFlags & DF_NOTHOUSANDSGROUP )
    {
        nmfmt.Grouping = 0;
    }

    // Format the number string
    WCHAR *pwszFormattedNum = new WCHAR[ cSize ];
    if ( !pwszFormattedNum )
    {
        return E_OUTOFMEMORY;
    }
    *pwszFormattedNum = 0;

    int iRet;
    do
    {
        iRet = m_Unicode.GetNumberFormat( LOCALE_USER_DEFAULT, 0, 
            pwszNum, &nmfmt, pwszFormattedNum, cSize );
        if ( !iRet && nmfmt.NumDigits )
        {
            // Try displaying fewer digits
            nmfmt.NumDigits--;
        }
    }   while ( !iRet && nmfmt.NumDigits );
    SPDBG_ASSERT( iRet );

    // Copy the formatted number into pwszNum
    wcscpy( pwszNum, pwszFormattedNum );
    delete[] pwszFormattedNum;

    // This undoes the hack of changing the first digit
    if ( fChangedFirstDigit )
    {
        // We need to find the first digit and change it back to zero
        WCHAR *pwc = wcschr( pwszNum, wcFakeFirstDigit );
        SPDBG_ASSERT( pwc );
        *pwc = L'0';
    }

    if ( dwDisplayFlags & DF_ORDINAL )
    {
        SPDBG_ASSERT( dwDisplayFlags & DF_WHOLENUMBER );    // sanity

        // This is an ordinal number, tack on the appropriate suffix
        
        // The "st", "nd", "rd" endings only happen when you
        // don't have something like "twelfth"
        if ( ((llIntPart % 100) < 10) || ((llIntPart % 100) > 20) )
        {
            switch ( llIntPart % 10 )
            {
            case 1:
                wcscat( pwszNum, L"st" );
                break;
            case 2:
                wcscat( pwszNum, L"nd" );
                break;
            case 3:
                wcscat( pwszNum, L"rd" );
                break;
            default:
                wcscat( pwszNum, L"th" );
                break;
            }
        }
        else
        {
            wcscat( pwszNum, L"th" );
        }
    }

    return S_OK;

}   /* CTestITN::MakeDisplayNumber */

/***********************************************************************
* CTestITN::MakeDigitString *
*---------------------------*
*   Description:
*       Called when we want to convert a string of DIGITs into
*       a string but don't care about its value
*   Return: 
*       Number of digits written to the string, including nul
*       character
*************************************************************************/
int CTestITN::MakeDigitString( const SPPHRASEPROPERTY *pProperties,
                              WCHAR *pwszDigitString,
                              UINT cSize )
{
    if ( !pProperties || !pwszDigitString )
    {
        return 0;
    }

    UINT cCount = 0;
    for ( ; pProperties; pProperties = pProperties->pNextSibling )
    {
        if ( DIGIT != pProperties->ulId )
        {
            return 0;
        }

        if ( cSize-- <= 0 )
        {
            // Not enough space
            return 0;
        }
        pwszDigitString[ cCount++ ] = pProperties->pszValue[0];
    }

    pwszDigitString[ cCount++ ] = 0;

    return cCount;
}   /* CTestITN::MakeDigitString */

/***********************************************************************
* CTestITN::GetNumberFormatDefaults *
*-----------------------------------*
*   Description:
*       This finds all of the defaults for formatting numbers for
*        this user.
*************************************************************************/
HRESULT CTestITN::GetNumberFormatDefaults()
{
    LCID lcid = ::GetUserDefaultLCID();
    WCHAR pwszLocaleData[ MAX_LOCALE_DATA ];
    
    int iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_IDIGITS, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_nmfmtDefault.NumDigits = _wtoi( pwszLocaleData );    
    
    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_ILZERO, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    // It's always either 0 or 1
    m_nmfmtDefault.LeadingZero = _wtoi( pwszLocaleData );

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SGROUPING, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    // It will look like single_digit;0, or else it will look like
    // 3;2;0
    UINT uiGrouping = *pwszLocaleData - L'0';
    if ( (3 == uiGrouping) && (L';' == pwszLocaleData[1]) && (L'2' == pwszLocaleData[2]) )
    {
        uiGrouping = 32;   
    }
    m_nmfmtDefault.Grouping = uiGrouping;

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SDECIMAL, m_pwszDecimalSep, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_nmfmtDefault.lpDecimalSep = m_pwszDecimalSep;

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_STHOUSAND, m_pwszThousandSep, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_nmfmtDefault.lpThousandSep = m_pwszThousandSep;

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_INEGNUMBER, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_nmfmtDefault.NegativeOrder = _wtoi( pwszLocaleData );

    // Get the negative sign
    delete[] m_pwszNeg;
    iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, 
        LOCALE_SNEGATIVESIGN, NULL, 0);
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_pwszNeg = new WCHAR[ iRet ];
    if ( !m_pwszNeg )
    {
        return E_OUTOFMEMORY;
    }
    iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, 
        LOCALE_SNEGATIVESIGN, m_pwszNeg, iRet );

    return iRet ? S_OK : E_FAIL;
}   /* CTestITN::GetNumberFormatDefaults */

/***********************************************************************
* CTestITN::GetCurrencyFormatDefaults *
*-----------------------------------*
*   Description:
*       This finds all of the defaults for formatting numbers for
*        this user.
*************************************************************************/
HRESULT CTestITN::GetCurrencyFormatDefaults()
{
    LCID lcid = ::GetUserDefaultLCID();
    WCHAR pwszLocaleData[ MAX_LOCALE_DATA ];
    
    int iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_ICURRDIGITS, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_cyfmtDefault.NumDigits = _wtoi( pwszLocaleData );    

    // NB: A value of zero is bogus for LOCALE_ILZERO, since
    // currency should always display leading zero
    m_cyfmtDefault.LeadingZero = 1;

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SMONGROUPING, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    // It will look like single_digit;0, or else it will look like
    // 3;2;0
    UINT uiGrouping = *pwszLocaleData - L'0';
    if ( (3 == uiGrouping) && (L';' == pwszLocaleData[1]) && (L'2' == pwszLocaleData[2]) )
    {
        uiGrouping = 32;   
    }
    m_cyfmtDefault.Grouping = uiGrouping;

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SMONDECIMALSEP, m_pwszDecimalSep, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_cyfmtDefault.lpDecimalSep = m_pwszDecimalSep;

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SMONTHOUSANDSEP, m_pwszThousandSep, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_cyfmtDefault.lpThousandSep = m_pwszThousandSep;

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_INEGCURR, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_cyfmtDefault.NegativeOrder = _wtoi( pwszLocaleData );

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_ICURRENCY, pwszLocaleData, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_cyfmtDefault.PositiveOrder = _wtoi( pwszLocaleData );

    iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SCURRENCY, m_pwszCurrencySym, MAX_LOCALE_DATA );
    if ( !iRet )
    {
        return E_FAIL;
    }
    m_cyfmtDefault.lpCurrencySymbol = m_pwszCurrencySym;


    return S_OK;
}   /* CTestITN::GetCurrencyFormatDefaults */

/***********************************************************************
* CTestITN::ComputeNum999 *
*-------------------------*
*   Description:
*       Converts a set of SPPHRASEPROPERTYs into a number in
*       [-999, 999].
*       The way these properties is structured is that the top-level 
*       properties contain the place of the number (100s, 10s, 1s)
*       by having the value 100, 10, or 1.
*       The child has the appropriate number value.
*   Return:
*       Value of the number
*************************************************************************/
ULONG CTestITN::ComputeNum999(const SPPHRASEPROPERTY *pProperties )//, ULONG *pVal)
{
    ULONG ulVal = 0;

    for (const SPPHRASEPROPERTY * pProp = pProperties; pProp; pProp = pProp->pNextSibling)
    {
        if ( ZERO != pProp->ulId )
        {
            SPDBG_ASSERT( pProp->pFirstChild );
            SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt );
            SPDBG_ASSERT( VT_UI4 == pProp->pFirstChild->vValue.vt );

            ulVal += pProp->pFirstChild->vValue.ulVal * pProp->vValue.ulVal;
        }
    }
    return ulVal;
}   /* CTestITN::ComputeNum999 */

/***********************************************************************
* CTestITN::HandleDigitsAfterDecimal *
*------------------------------------*
*   Description:
*       If pwszRightOfDecimal is NULL, then cuts off all of the numbers
*       following the decimal separator.
*       Otherwise, copies pwszRightOfDecimal right after the decimal
*       separator in pwszFormattedNum.
*       Preserves the stuff after the digits in the pwszFormattedNum
*       (e.g. if pwszFormattedNum starts out "(3.00)" and 
*       pwszRightOfDecimal is NULL, then pwszFormattedNum will end
*       up as "(3)"  
*************************************************************************/
void CTestITN::HandleDigitsAfterDecimal( WCHAR *pwszFormattedNum, 
                              UINT cSizeOfFormattedNum, 
                              const WCHAR *pwszRightOfDecimal )
{
    SPDBG_ASSERT( pwszFormattedNum );
 
    // First need to find what the decimal string is
    LCID lcid = ::GetUserDefaultLCID();
    WCHAR pwszDecimalString[ MAX_LOCALE_DATA ];    // Guaranteed to be no longer than 4 long
    int iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SDECIMAL, 
        pwszDecimalString, MAX_LOCALE_DATA );
    SPDBG_ASSERT( iRet );

    WCHAR *pwcDecimal = wcsstr( pwszFormattedNum, pwszDecimalString );
    SPDBG_ASSERT( pwcDecimal );

    // pwcAfterDecimal points to the first character after the decimal separator
    WCHAR *pwcAfterDecimal = pwcDecimal + wcslen( pwszDecimalString );

    // Remember what originally followed the digits
    WCHAR *pwszTemp = new WCHAR[ cSizeOfFormattedNum ];
    WCHAR *pwcAfterDigits;  // points to the first character after the end of the digits
    for ( pwcAfterDigits = pwcAfterDecimal; 
        *pwcAfterDigits && iswdigit( *pwcAfterDigits ); 
        pwcAfterDigits++ )
        ;
    wcscpy( pwszTemp, pwcAfterDigits ); // OK if *pwcAfterDigits == 0

    if ( pwszRightOfDecimal )
    {
        // This means that the caller wants the digits in pwszRightOfDecimal
        // copied after the decimal separator

        // Copy the decimal string after the decimal separater
        wcscpy( pwcAfterDecimal, pwszRightOfDecimal );

    }
    else
    {
        // This means that the caller wanted the decimal separator
        // and all text following it stripped off

        *pwcDecimal = 0;
    }

    // Add on the extra after-digit characters
    wcscat( pwszFormattedNum, pwszTemp );

    delete[] pwszTemp;

}   /* CTestITN::HandleDigitsAfterDecimal */


/***********************************************************************
* CTestITN::GetMinAndMaxPos *
*---------------------------*
*   Description:
*       Gets the minimum and maximum elements spanned by the 
*       set of properties
*************************************************************************/
void CTestITN::GetMinAndMaxPos( const SPPHRASEPROPERTY *pProperties, 
                     ULONG *pulMinPos, 
                     ULONG *pulMaxPos )
{
    if ( !pulMinPos || !pulMaxPos )
    {
        return;
    }
    ULONG ulMin = 9999999;
    ULONG ulMax = 0;

    for ( const SPPHRASEPROPERTY *pProp = pProperties; pProp; pProp = pProp->pNextSibling )
    {
        ulMin = __min( ulMin, pProp->ulFirstElement );
        ulMax = __max( ulMax, pProp->ulFirstElement + pProp->ulCountOfElements );
    }
    *pulMinPos = ulMin;
    *pulMaxPos = ulMax;
}   /* CTestITN::GetMinAndMaxPos */

/***********************************************************************
* CTestITN::GetMonthName *
*------------------------*
*   Description:
*       Gets the name of the month, abbreviated if desired
*   Return:
*       Number of characters written to pszMonth, 0 if failed
*************************************************************************/
int CTestITN::GetMonthName( int iMonth, WCHAR *pwszMonth, int cSize, bool fAbbrev )
{
    LCTYPE lctype;
    switch ( iMonth )
    {
    case 1:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME1 : LOCALE_SMONTHNAME1;
        break;
    case 2:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME2 : LOCALE_SMONTHNAME2;
        break;
    case 3:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME3 : LOCALE_SMONTHNAME3;
        break;
    case 4:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME4 : LOCALE_SMONTHNAME4;
        break;
    case 5:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME5 : LOCALE_SMONTHNAME5;
        break;
    case 6:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME6 : LOCALE_SMONTHNAME6;
        break;
    case 7:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME7 : LOCALE_SMONTHNAME7;
        break;
    case 8:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME8 : LOCALE_SMONTHNAME8;
        break;
    case 9:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME9 : LOCALE_SMONTHNAME9;
        break;
    case 10:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME10 : LOCALE_SMONTHNAME10;
        break;
    case 11:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME11 : LOCALE_SMONTHNAME11;
        break;
    case 12:
        lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME12 : LOCALE_SMONTHNAME12;
        break;
    default:
        return 0;
    }

    int iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, lctype, pwszMonth, cSize );

    return iRet;
}   /* CTestITN::GetMonthName */

/***********************************************************************
* CTestITN::GetDayOfWeekName *
*----------------------------*
*   Description:
*       Gets the name of the day of week, abbreviated if desired
*   Return:
*       Number of characters written to pszDayOfWeek, 0 if failed
*************************************************************************/
int CTestITN::GetDayOfWeekName( int iDayOfWeek, 
                               WCHAR *pwszDayOfWeek, 
                               int cSize, 
                               bool fAbbrev )
{
    LCTYPE lctype;
    switch ( iDayOfWeek )
    {
    case 1:
        lctype = fAbbrev ? LOCALE_SABBREVDAYNAME1 : LOCALE_SDAYNAME1;
        break;
    case 2:
        lctype = fAbbrev ? LOCALE_SABBREVDAYNAME2 : LOCALE_SDAYNAME2;
        break;
    case 3:
        lctype = fAbbrev ? LOCALE_SABBREVDAYNAME3 : LOCALE_SDAYNAME3;
        break;
    case 4:
        lctype = fAbbrev ? LOCALE_SABBREVDAYNAME4 : LOCALE_SDAYNAME4;
        break;
    case 5:
        lctype = fAbbrev ? LOCALE_SABBREVDAYNAME5 : LOCALE_SDAYNAME5;
        break;
    case 6:
        lctype = fAbbrev ? LOCALE_SABBREVDAYNAME6 : LOCALE_SDAYNAME6;
        break;
    case 7:
        lctype = fAbbrev ? LOCALE_SABBREVDAYNAME7 : LOCALE_SDAYNAME7;
        break;
    default:
        return 0;
    }

    int iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, lctype, 
        pwszDayOfWeek, cSize );

    return iRet;
}   /* CTestITN::GetMonthName */

/***********************************************************************
* CTestITN::FormatDate *
*----------------------*
*   Description:
*       Uses the format string to format a SYSTEMTIME date.
*       We are using this instead of GetDateFormat() since
*       we also want to format bogus dates and dates with 
*       years like 1492 that are not accepted by GetDateFormat()
*   Return:
*       Number of characters written to pszDate (including
*       null terminating character), 0 if failed
*************************************************************************/
int CTestITN::FormatDate( const SYSTEMTIME &stDate, 
               WCHAR *pwszFormat,
               WCHAR *pwszDate, 
               int cSize )
{
    if ( !pwszFormat || !pwszDate )
    {
        SPDBG_ASSERT( FALSE );
        return 0;
    }

    WCHAR * const pwszDateStart = pwszDate;

    WCHAR *pwc = pwszFormat;

    // Copy the format string to the date string character by 
    // character, replacing the string like "dddd" as appropriate
    while ( *pwc )
    {
        switch( *pwc )
        {
        case L'd':
            {
                // Count the number of d's
                int cNumDs = 0;
                int iRet;
                do
                {
                    pwc++;
                    cNumDs++;
                }   while ( L'd' == *pwc );
                switch ( cNumDs )
                {
                case 1: 
                    // Day with no leading zeroes
                    swprintf( pwszDate, L"%d", stDate.wDay );
                    iRet = wcslen( pwszDate );
                    break;
                case 2:
                    // Day with one fixed width of 2
                    swprintf( pwszDate, L"%02d", stDate.wDay );
                    iRet = wcslen( pwszDate );
                    break;
                case 3:
                    // Abbreviated day of week
                    iRet = GetDayOfWeekName( stDate.wDayOfWeek, pwszDate, cSize, true ) - 1;
                    break;
                default: // More than 4?  Treat it as 4
                    // Day of week
                    iRet = GetDayOfWeekName( stDate.wDayOfWeek, pwszDate, cSize, false ) - 1;
                    break;
                }

                if ( iRet <= 0 )
                {
                    return 0;
                }
                else
                {
                    pwszDate += iRet;
                }
                break;
            }

        case L'M':
            {
                // Count the number of M's
                int cNumMs = 0;
                int iRet;
                do
                {
                    pwc++;
                    cNumMs++;
                }   while ( L'M' == *pwc );
                switch ( cNumMs )
                {
                case 1: 
                    // Day with no leading zeroes
                    swprintf( pwszDate, L"%d", stDate.wMonth );
                    iRet = wcslen( pwszDate );
                    break;
                case 2:
                    // Day with one fixed width of 2
                    swprintf( pwszDate, L"%02d", stDate.wMonth );
                    iRet = wcslen( pwszDate );
                    break;
                case 3:
                    // Abbreviated month name
                    iRet = GetMonthName( stDate.wMonth, pwszDate, cSize, true ) - 1;
                    break;
                default: // More than 4?  Treat it as 4
                    // Month
                    iRet = GetMonthName( stDate.wMonth, pwszDate, cSize, false ) - 1;
                    break;
                }

                if ( iRet < 0 )
                {
                    return 0;
                }
                else
                {
                    pwszDate += iRet;
                }
                break;
            }
            
        case L'y':
            {
                // Count the number of y's
                int cNumYs = 0;
                do
                {
                    pwc++;
                    cNumYs++;
                }   while ( L'y' == *pwc );

                // More than 4 y's: consider it as 4 y's
                if ( cNumYs > 4 )
                {
                    cNumYs = 4;
                }

                if (( cNumYs >= 3 ) && ( stDate.wYear < 100 ))
                {
                    // "Ninety nine": Should display as "'99"
                    cNumYs = 2;

                    *pwszDate++ = L'\'';
                }

                switch ( cNumYs )
                {
                case 1: case 2: 
                    // Last two digits of year, width of 2
                    swprintf( pwszDate, (1 == cNumYs ) ? L"%d" : L"%02d", 
                        stDate.wYear % 100 );
                    pwszDate += 2;
                    break;
                case 3: case 4:
                    // All four digits of year, width of 4
                    // Last two digits of year, width of 2
                    swprintf( pwszDate, L"%04d", stDate.wYear % 10000 );
                    pwszDate += 4;
                    break;
                }
                break;
            }

        default:
            *pwszDate++ = *pwc++;
        }
    }
    
    *pwszDate++ = 0;

    return (int) (pwszDate - pwszDateStart);
}   /* CTestITN::FormatDate */

/***********************************************************************
* CTestITN::MakeNumberNegative *
*------------------------------*
*   Description:
*       Uses the current number format defaults to transform
*       pszNumber into a negative number
*   Return:
*       S_OK
*       E_OUTOFMEMORY
*************************************************************************/
HRESULT CTestITN::MakeNumberNegative( WCHAR *pwszNumber )
{
    HRESULT hr = GetNumberFormatDefaults();
    if ( FAILED( hr ) )
    {
        return hr;
    }

    // Create a temporary buffer with the non-negated number in it
    WCHAR *pwszTemp = wcsdup( pwszNumber );
    if ( !pwszTemp )
    {
        return E_OUTOFMEMORY;
    }

    switch ( m_nmfmtDefault.NegativeOrder )
    {
    case 0:
        // (1.1)
        wcscpy( pwszNumber, L"(" );
        wcscat( pwszNumber, pwszTemp );
        wcscat( pwszNumber, L")" );
        break;

    case 1: case 2:
        // 1: -1.1  2: - 1.1
        wcscpy( pwszNumber, m_pwszNeg );
        if ( 2 == m_nmfmtDefault.NegativeOrder )
        {
            wcscat( pwszNumber, L" " );
        }
        wcscat( pwszNumber, pwszTemp );
        break;

    case 3: case 4:
        // 3: 1.1-  4: 1.1 -
        wcscpy( pwszNumber, pwszTemp );
        if ( 4 == m_nmfmtDefault.NegativeOrder )
        {
            wcscat( pwszNumber, L" " );
        }
        wcscat( pwszNumber, m_pwszNeg );
        break;

    default:
        SPDBG_ASSERT( false );
        break;
    }

    free( pwszTemp );

    return S_OK;
}   /* CTestITN::MakeNumberNegative */