Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2779 lines
88 KiB

// 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 */