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