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.
 
 
 
 
 
 

2017 lines
64 KiB

// TestITN_J.cpp : Implementation of CTestITN_J
#include "stdafx.h"
#include <winnls.h>
#include "Itngram_J.h"
#include "TestITN_J.h"
#include "sphelper.h"
#include "spddkhlp.h"
#include "test_j.h"
#define MAX_SIG_FIGS 12
#define MANN ((LONGLONG) 10000)
#define OKU ((LONGLONG) 100000000)
#define CHUU ((LONGLONG) 1000000000000)
#define MANN_STR (L"\x4E07")
#define OKU_STR (L"\x5104")
#define CHUU_STR (L"\x5146")
const WCHAR s_pwszDegree[] = { 0xb0, 0 };
const WCHAR s_pwszMinute[] = { 0x2032, 0 };
const WCHAR s_pwszSecond[] = { 0x2033, 0 };
#define DAYOFWEEK_STR_ABBR ("ddd")
#define DAYOFWEEK_STR ("dddd")
/////////////////////////////////////////////////////////////////////////////
// CTestITN_J
/****************************************************************************
* CTestITN_J::InitGrammar *
*-------------------------*
* Description:
*
* Returns:
*
********************************************************************* RAL ***/
STDMETHODIMP CTestITN_J::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_J::Interpret *
*-----------------------*
* Description:
*
* Returns:
*
********************************************************************* RAL ***/
STDMETHODIMP CTestITN_J::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;
//Just use ulFirstElement & ulCountOfElements
// Get the minimum and maximum positions
ULONG ulMinPos;
ULONG ulMaxPos;
//GetMinAndMaxPos( cpPhrase->pProperties, &ulMinPos, &ulMaxPos );
ulMinPos = ulFirstElement;
ulMaxPos = ulMinPos + ulCountOfElements;
if (SUCCEEDED(hr))
{
hr = S_FALSE;
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 );
if (SUCCEEDED( hr ) && ( dblValue >= 0 ) && ( dblValue <= 20 )
&& ( GRID_DIGIT_NUMBER != cpPhrase->pProperties->ulId ))
{
// Throw this one out because numbers like "three"
// shouldn't be ITNed by themselves
hr = S_FALSE;
goto Cleanup; // no replacement
}
break;
case GRID_INTEGER: case GRID_INTEGER_9999: case GRID_ORDINAL:// Number
hr = InterpretNumber( cpPhrase->pProperties, true,
&dblValue, pszValueBuff, MAX_PATH );
break;
case GRID_DIGIT_NUMBER: // Number "spelled out" digit by digit
hr = InterpretDigitNumber( cpPhrase->pProperties,
&dblValue, pszValueBuff, MAX_PATH );
break;
case GRID_FP_NUMBER:
hr = InterpretFPNumber( cpPhrase->pProperties,
&dblValue, pszValueBuff, MAX_PATH );
break;
case GRID_DENOMINATOR:
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_CURRENCY: // Currency
hr = InterpretCurrency( cpPhrase->pProperties,
&dblValue, pszValueBuff, MAX_PATH );
break;
case GRID_TIME:
hr = InterpretTime( cpPhrase->pProperties,
&dblValue, 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;
default:
_ASSERT(FALSE);
break;
}
if ( SUCCEEDED( hr ) )
{
hr = AddPropertyAndReplacement( pszValueBuff, dblValue,
ulMinPos, ulMaxPos, ulMinPos, ulMaxPos - ulMinPos );//ulFirstElement, ulCountOfElements );
return hr;
}
}
Cleanup:
return S_FALSE;
}
/***********************************************************************
* CTestITN_J::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_J::InterpretNumber(const SPPHRASEPROPERTY *pProperties,
const bool fCardinal,
DOUBLE *pdblVal,
WCHAR *pszVal,
UINT cSize)
{
if ( !pdblVal || !pszVal )
{
return E_POINTER;
}
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 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;
UINT uiFixedWidth = wcslen( pFirstProp->pszValue );
*pdblVal = dblVal * iPositive;
DWORD dwDisplayFlags = DF_WHOLENUMBER | DF_FIXEDWIDTH | DF_NOTHOUSANDSGROUP;
return MakeDisplayNumber( *pdblVal, dwDisplayFlags,
uiFixedWidth, 0, pszVal, MAX_PATH, FALSE );
}
for (const SPPHRASEPROPERTY * pProp = pFirstProp; pProp; pProp ? pProp = pProp->pNextSibling : NULL)
{
switch(pProp->ulId)
{
case ICHIs:
{
SPDBG_ASSERT(pProp->pFirstChild);
llValue += ComputeNum9999( pProp->pFirstChild );
}
break;
case MANNs:
{
llValue += ComputeNum9999( pProp->pFirstChild ) * 10000;
}
break;
case OKUs:
{
SPDBG_ASSERT(pProp->pFirstChild);
llValue += ComputeNum9999( pProp->pFirstChild ) * (LONGLONG) 1e8;
}
break;
case CHOOs:
{
SPDBG_ASSERT(pProp->pFirstChild);
llValue += ComputeNum9999( pProp->pFirstChild ) * (LONGLONG) 1e12;
}
break;
default:
SPDBG_ASSERT(false);
}
}
llValue *= iPositive;
*pdblVal = (DOUBLE) llValue;
#if 0
if ( !pProperties->pNextSibling && ( (BILLIONS == pProperties->ulId) || (MILLIONS == pProperties->ulId) ) )
{
// This is something like "3 billion", which should be displayed that way
return E_NOTIMPL;
}
else
#endif
{
DWORD dwDisplayFlags = DF_WHOLENUMBER | (fCardinal ? 0 : DF_ORDINAL);
//Special code to handle minus 0.
if ((iPositive == -1) && (*pdblVal == 0.0f))
{
*pszVal = L'-';
*(pszVal+1) = 0;
return MakeDisplayNumber( *pdblVal, dwDisplayFlags, 0, 0, pszVal+1, cSize-1, FALSE );
}
else
{
return MakeDisplayNumber( *pdblVal, dwDisplayFlags, 0, 0, pszVal, cSize, FALSE );
}
}
} /* CTestITN_J::InterpretNumber */
/***********************************************************************
* CTestITN_J::InterpretDigitNumber *
*----------------------------------*
* Description:
* Interprets an integer in (-INF, +INF) that has been spelled
* out digit by digit.
* Result:
*************************************************************************/
HRESULT CTestITN_J::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++ = L'0' + pProp->vValue.iVal;
cLen++;
break;
}
default:
SPDBG_ASSERT(false);
}
}
*pwc = 0;
SPDBG_ASSERT( cLen <= MAX_SIG_FIGS );
*pdblVal = (DOUBLE) _wtoi64( pszVal );
return S_OK;
} /* CTestITN_J::InterpretDigitNumber */
/***********************************************************************
* CTestITN_J::InterpretFPNumber *
*-------------------------------*
* Description:
* Interprets a floating-point number of up to MAX_SIG_FIGS sig
* figs. Truncates the floating-point part as necessary
* The way the grammar is structured, there will be an optional
* ONES property, whose value will already have been interpreted,
* as well as a mandatory FP_PART property, whose value will
* be divided by the appropriate multiple of 10.
* Result:
* Return value of CTestITN_J::AddPropertyAndReplacement()
*************************************************************************/
HRESULT CTestITN_J::InterpretFPNumber( const SPPHRASEPROPERTY *pProperties,
DOUBLE *pdblVal,
WCHAR *pszVal,
UINT cSize)
{
if ( !pdblVal || !pszVal )
{
return E_POINTER;
}
SPDBG_ASSERT( pProperties );
UINT uiSigFigs = 0;
*pdblVal = 0;
*pszVal = 0;
DWORD dwDisplayFlags = 0;
BOOL bOverWriteNOTHOUSANDSGROUP = FALSE;
const SPPHRASEPROPERTY *pProp = pProperties;
UINT uiFixedWidth = 0;
TCHAR pwszLocaleData[ MAX_LOCALE_DATA ];
int iRet = ::GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_ILZERO, pwszLocaleData, MAX_LOCALE_DATA );
if ( !iRet )
{
return E_FAIL;
}
if (atoi( pwszLocaleData ))
{
dwDisplayFlags |= DF_LEADINGZERO;
}
// Minus sign?
if (NEGATIVE == pProp->ulId )
{
uiSigFigs = 1;
// Go to the next property
pProp = pProp->pNextSibling;
}
// ONES is optional since "point five" should be ".5" if the user perfers
if ( ICHIs == pProp->ulId )
{
// Get the value
SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
*pdblVal = pProp->vValue.dblVal;
uiSigFigs = (pProp->pszValue[0] == L'-'); // Have to take care of the case of -0.05
if (uiSigFigs)
{
*pdblVal = -*pdblVal;
}
// Count up the width of the number and set the fixed width flag
dwDisplayFlags |= DF_FIXEDWIDTH;
const WCHAR *pwc;
for ( uiFixedWidth = 0, pwc = pProp->pszValue; *pwc; pwc++ )
{
if ( iswdigit( *pwc ) )
{
uiFixedWidth++;
}
}
if (!iswdigit( pProp->pszValue[wcslen(pProp->pszValue) - 1] ))
{
//Ends with Mann, Choo,..
bOverWriteNOTHOUSANDSGROUP = TRUE;
}
// This needs to be here in case the user said "zero"
dwDisplayFlags |= DF_LEADINGZERO;
// If there is no thousands separator in its string value,
// then leave out the thousands separator in the result
USES_CONVERSION;
TCHAR pszThousandSep[ MAX_LOCALE_DATA ];
::GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,
pszThousandSep, MAX_LOCALE_DATA );
if ( NULL == wcsstr( pProp->pszValue, T2W( pszThousandSep ) ) && !bOverWriteNOTHOUSANDSGROUP)
{
dwDisplayFlags |= DF_NOTHOUSANDSGROUP;
}
// Go to the next property
pProp = pProp->pNextSibling;
}
else if ( ZERO == pProp->ulId )
{
// "oh point..."
// This will force a leading zero
dwDisplayFlags |= DF_LEADINGZERO;
pProp = pProp->pNextSibling;
}
UINT uiDecimalPlaces = 0;
if ( pProp && (FP_PART == pProp->ulId) )
{
// Deal with the stuff to the right of the decimal point
// Count up the number of decimal places, and for each
// decimal place divide the value by 10
// (e.g. 83 gets divided by 100).
SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
DOUBLE dblRightOfDecimal = pProp->vValue.dblVal;
const WCHAR *pwc;
for ( uiDecimalPlaces = 0, pwc = pProp->pszValue; *pwc; pwc++ )
{
if ( iswdigit( *pwc ) )
{
dblRightOfDecimal /= (DOUBLE) 10;
uiDecimalPlaces++;
}
}
*pdblVal += dblRightOfDecimal;
}
else if ( pProp && (FP_PART_D == pProp->ulId) )
{
// The user said "point" and one digit
SPDBG_ASSERT( VT_UI4 == pProp->pFirstChild->vValue.vt );
uiDecimalPlaces = 1;
if ( *pdblVal >= 0 )
{
*pdblVal += pProp->pFirstChild->vValue.iVal / 10.0;
}
else
{
*pdblVal -= pProp->pFirstChild->vValue.iVal / 10.0;
}
}
if (uiSigFigs)
{
*pdblVal = -*pdblVal;
}
MakeDisplayNumber( *pdblVal, dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pszVal, cSize, FALSE );
return S_OK;
} /* CTestITN_J::InterpretFPNumber */
/***********************************************************************
* CTestITN_J::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_J::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;
BOOL bNegativeDenominator = FALSE;
WCHAR wszBuffer[MAX_PATH];
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 );
// Do we need to re-generate the numbers?
if (!iswdigit( pszVal[wcslen(pszVal) - 1] ))
{
MakeDisplayNumber( dblWholeValue, DF_WHOLENUMBER, 0, 0, pszVal, MAX_PATH, TRUE );
}
// Add a space between the whole part and the fractional part
wcscat( pszVal, L" " );
SPDBG_ASSERT( pProp->pNextSibling );
pProp = pProp->pNextSibling;
}
// Nothing is optional in Japanese, However, the order is different from English
SPDBG_ASSERT( DENOMINATOR == pProp->ulId );
// Look ahead to see if it is a nagative number
bNegativeDenominator = (pProp->vValue.dblVal < 0);
for( pProp = pProperties; NUMERATOR != pProp->ulId; pProp = pProp->pNextSibling );
if( NUMERATOR == pProp->ulId)
{
SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
dblFracValue = pProp->vValue.dblVal;
if (bNegativeDenominator && (pProp->vValue.dblVal >= 0))
{
//put the minus sign here.
wcscat( pszVal, L"-");
bNegativeDenominator = FALSE;
}
// Do we need to re-generate the numbers?
if (!iswdigit( pProp->pszValue[wcslen(pProp->pszValue) - 1] ))
{
MakeDisplayNumber( dblFracValue, DF_WHOLENUMBER, 0, 0, wszBuffer, MAX_PATH, TRUE );
wcscat( pszVal, wszBuffer );
}
else
{
wcscat( pszVal, pProp->pszValue );
}
}
else
{
// No numerator, assume 1
wcscat( pszVal, L"1" );
}
wcscat( pszVal, L"/" );
for( pProp = pProperties; DENOMINATOR != pProp->ulId; pProp = pProp->pNextSibling );
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;
if (!bNegativeDenominator && (pProp->vValue.dblVal<0))
{
// Do we need to re-generate the numbers?
if (!iswdigit( pProp->pszValue[wcslen(pProp->pszValue) - 1] ))
{
MakeDisplayNumber( -pProp->vValue.dblVal, DF_WHOLENUMBER, 0, 0, wszBuffer, MAX_PATH, TRUE );
wcscat( pszVal, wszBuffer );
}
else
{
wcscat( pszVal, pProp->pszValue+1 );
}
}
else
{
// Do we need to re-generate the numbers?
if (!iswdigit( pProp->pszValue[wcslen(pProp->pszValue) - 1] ))
{
MakeDisplayNumber( pProp->vValue.dblVal, DF_WHOLENUMBER, 0, 0, wszBuffer, MAX_PATH, TRUE );
wcscat( pszVal, wszBuffer );
}
else
{
wcscat( pszVal, pProp->pszValue );
}
}
// Tack on the ")" or "-" from the end of the numerator
wcscat( pszVal, pszTemp );
*pdblVal = dblWholeValue + dblFracValue;
return S_OK;
} /* CTestITN_J::InterpretFraction */
/***********************************************************************
* CTestITN_J::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).
* The Japanese Grammar will not accept invalid date.
*************************************************************************/
HRESULT CTestITN_J::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
if ( 0 == ::GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SLONGDATE,
m_pszLongDateFormat, MAX_DATE_FORMAT ) )
{
return E_FAIL;
}
SYSTEMTIME stDate;
memset( (void *) &stDate, 0, sizeof( stDate ) );
// Arguments for ::GetDateFormat()
TCHAR *pszFormatArg = NULL;
TCHAR pszFormat[ MAX_DATE_FORMAT ];
const SPPHRASEPROPERTY *pProp;
const SPPHRASEPROPERTY *pPropChild;
const WCHAR* pwszEmperor;
// Get the month
for ( pProp = pProperties; pProp && ( GATSU != pProp->ulId ); pProp = pProp->pNextSibling )
;
SPDBG_ASSERT( pProp ); //There should be a month, and the grammar is forcing it
SPDBG_ASSERT( pProp->pFirstChild );
pPropChild = pProp->pFirstChild;
SPDBG_ASSERT( VT_UI4 == pPropChild->vValue.vt );
SPDBG_ASSERT( (1 <= pPropChild->vValue.ulVal) && (12 >= pPropChild->vValue.ulVal) );
if ( (pPropChild->vValue.ulVal < 1) || (pPropChild->vValue.ulVal > 12) )
{
return E_INVALIDARG;
}
stDate.wMonth = (WORD) pPropChild->vValue.ulVal;
// Get the emperor's name
for ( pProp = pProperties; pProp && ( IMPERIAL != pProp->ulId ); pProp = pProp->pNextSibling )
;
if ( pProp )
{
SPDBG_ASSERT( pProp );
pPropChild = pProp->pFirstChild;
pwszEmperor = pPropChild->pszValue;
}
else
{
pwszEmperor = 0;
}
// Get the year
for ( pProp = pProperties; pProp && ( NENN != pProp->ulId ); pProp = pProp->pNextSibling )
;
const SPPHRASEPROPERTY * const pPropYear = pProp;
if ( pProp )
{
SPDBG_ASSERT( pProp ); // There should be a year
SPDBG_ASSERT( VT_R8 == pProp->vValue.vt );
stDate.wYear = (WORD) pProp->vValue.dblVal;
}
// Attempt to get the day of month
for ( pProp = pProperties; pProp && ( NICHI != pProp->ulId ); pProp = pProp->pNextSibling )
;
const SPPHRASEPROPERTY * const pPropDayOfMonth = pProp;
if ( pProp )
{
pPropChild = pProp->pFirstChild;
SPDBG_ASSERT( VT_UI4 == pPropChild->vValue.vt );
SPDBG_ASSERT( (1 <= pPropChild->vValue.ulVal) && (31 >= pPropChild->vValue.ulVal) );
if ( (pPropChild->vValue.ulVal < 1) || (pPropChild->vValue.ulVal > 31) )
{
return E_INVALIDARG;
}
stDate.wDay = (WORD) pPropChild->vValue.ulVal;
// Look for a day of week
for ( pProp = pProperties; pProp && ( YOUBI != pProp->ulId ); pProp = pProp->pNextSibling )
;
if ( pProp )
{
// Day of week present
pPropChild = pProp->pFirstChild;
SPDBG_ASSERT( VT_UI4 == pPropChild->vValue.vt );
SPDBG_ASSERT( 6 >= pPropChild->vValue.ulVal );
if ( pPropChild->vValue.ulVal > 6 )
{
return E_INVALIDARG;
}
stDate.wDayOfWeek = (WORD) pPropChild->vValue.ulVal;
// Use the full long date format
pszFormatArg = m_pszLongDateFormat;
// If the user did say the day of week but the format string does
// not called for the day of week being displayed anywhere,
// Write out the day of week at the END of the output.
if ( !_tcsstr( m_pszLongDateFormat, DAYOFWEEK_STR_ABBR ) )
{
_tcscat( m_pszLongDateFormat, " dddd" );
}
}
else
{
TCHAR pszDayOfWeekStr[ MAX_LOCALE_DATA];
// Remove the day of week from the current date format string
TCHAR *pc = _tcsstr( m_pszLongDateFormat, DAYOFWEEK_STR );
if ( pc )
{
_tcscpy( pszDayOfWeekStr, DAYOFWEEK_STR );
}
else if ( NULL != (pc = _tcsstr( m_pszLongDateFormat, DAYOFWEEK_STR_ABBR )) )
{
_tcscpy( pszDayOfWeekStr, DAYOFWEEK_STR_ABBR );
}
if ( pc )
{
// Copy over everything until this character info the format string
_tcsncpy( pszFormat, m_pszLongDateFormat, (pc - m_pszLongDateFormat) );
pszFormat[(pc - m_pszLongDateFormat)] = 0;
// Skip over the day of week until the next symbol
// (the way date format strings work, this is the first
// alphabetical symbol
pc += _tcslen( pszDayOfWeekStr );
while ( *pc && !_istalpha( *pc ) )
{
pc++;
}
// Copy over everything from here on out
_tcscat( pszFormat, pc );
//dwFlags = 0;
pszFormatArg = pszFormat;
}
else // We don't have DAY_OF_WEEK in either the display format nor the results.
{
pszFormatArg = m_pszLongDateFormat;
}
}
}
else
{
_tcscpy( pszFormat, "MMMM, yyyy" );
pszFormatArg = pszFormat;
}
// 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, pszFormatArg, pszVal, cSize, pwszEmperor );
if ( 0 == iRet )
{
return E_FAIL;
}
return S_OK;
} /* CTestITN_J::InterpretDate */
/***********************************************************************
* CTestITN_J::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_J::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 fPM = false;
bool fClockTime = true;
bool fMinuteMinus = false;
const SPPHRASEPROPERTY *pProp;
for ( pProp = pProperties; pProp; pProp = pProp->pNextSibling )
{
#if 1
switch ( pProp->ulId )
{
case GOZENN: // If it is PM, add the hour by 12
{
if (pProp->pszValue[0] == L'P')
{
fPM = TRUE;
}
dwFlags &= ~TIME_NOTIMEMARKER;
break;
}
case JI:
{
UINT uiHour = pProp->pFirstChild->vValue.uiVal;
stTime.wHour = (WORD) uiHour;
if (fPM && (stTime.wHour < 12))
{
stTime.wHour += 12;
}
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->pFirstChild->vValue.uiVal;
break;
}
case HUNN:
{
// Special case for :30 (”¼)
stTime.wMinute = 30;
break;
}
case SECOND:
{
stTime.wSecond += (WORD) pProp->pFirstChild->vValue.uiVal;
dwFlags &= ~TIME_NOSECONDS;
break;
}
case MINUTE_TAG:
{
// We only need to deal with the case of •ª‘O
if( pProp->pszValue[0] == L'-' )
{
fMinuteMinus = true;
}
break;
}
default:
SPDBG_ASSERT( false );
}
#endif
}
if (fMinuteMinus)
{
stTime.wMinute = 60 - stTime.wMinute;
stTime.wHour--;
}
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;
}
TCHAR *pszTime = new TCHAR[ cSize ];
if ( !pszTime )
{
return E_OUTOFMEMORY;
}
if (stTime.wHour >= 24)
{
stTime.wHour -= 24; //To avoid problems in GetTimeFormat
}
if (stTime.wHour >= 12) // Enable the TimeMarker if the time is in the afternoon
{
dwFlags &= ~TIME_NOTIMEMARKER;
}
int iRet = ::GetTimeFormat( LOCALE_USER_DEFAULT, dwFlags, &stTime, NULL,
pszTime, cSize );
USES_CONVERSION;
wcscpy( pszVal, T2W(pszTime) );
delete[] pszTime;
// 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_J::InterpretTime */
/***********************************************************************
* CTestITN_J::InterpretDegrees *
*------------------------------*
* Description:
* Interprets degrees as a angle-measurement or direction
* Return:
* S_OK
* E_POINTER
* E_INVALIDARG
*************************************************************************/
HRESULT CTestITN_J::InterpretDegrees( const SPPHRASEPROPERTY *pProperties,
DOUBLE *pdblVal,
WCHAR *pszVal,
UINT cSize )
{
if ( !pProperties || !pdblVal || !pszVal )
{
return E_POINTER;
}
WCHAR *pwchDirection = 0;
*pszVal = 0;
//Do we have those direction tags
if (DIRECTION_TAG == pProperties->ulId)
{
pwchDirection = (WCHAR*) pProperties->pszValue;
pProperties = pProperties->pNextSibling;
}
// Get the number
*pdblVal = pProperties->vValue.dblVal;
wcscat( pszVal, pProperties->pszValue );
wcscat( pszVal, s_pwszDegree );
pProperties = pProperties->pNextSibling;
if ( pProperties && (MINUTE == pProperties->ulId ) )
{
SPDBG_ASSERT( *pdblVal >= 0 );
DOUBLE dblMin = pProperties->vValue.dblVal;
*pdblVal += dblMin / (DOUBLE) 60;
wcscat( pszVal, pProperties->pszValue );
wcscat( pszVal, s_pwszMinute );
pProperties = pProperties->pNextSibling;
}
if ( pProperties && (SECOND == pProperties->ulId) )
{
DOUBLE dblSec = pProperties->vValue.dblVal;
*pdblVal += dblSec / (DOUBLE) 3600;
wcscat( pszVal, pProperties->pszValue );
wcscat( pszVal, s_pwszSecond );
pProperties = pProperties->pNextSibling;
}
if (pwchDirection)
{
wcscat( pszVal, pwchDirection );
}
SPDBG_ASSERT( !pProperties );
return S_OK;
} /* CTestITN_J::InterpretDegrees */
/***********************************************************************
* CTestITN_J::InterpretMeasurement *
*----------------------------------*
* Description:
* Interprets measurements, which is a number followed
* by a units name
* Return:
* S_OK
* E_POINTER
* E_INVALIDARG
*************************************************************************/
HRESULT CTestITN_J::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 (!pPropUnits || !pPropNumber )
{
SPDBG_ASSERT( FALSE );
return E_INVALIDARG;
}
if ( cSize < (wcslen(pPropNumber->pszValue) + wcslen(pPropUnits->pszValue) + 1) )
{
// Not enough space
return E_INVALIDARG;
}
// Do we need to re-generate the numbers?
if (!iswdigit( pPropNumber->pszValue[wcslen(pPropNumber->pszValue) - 1] ))
{
MakeDisplayNumber( pPropNumber->vValue.dblVal, DF_WHOLENUMBER, 0, 0, pszVal, MAX_PATH, TRUE );
}
else
{
wcscpy(pszVal, pPropNumber->pszValue );
}
wcscat( pszVal, pPropUnits->pszValue );
*pdblVal = pPropNumber->vValue.dblVal;
return S_OK;
} /* CTestITN_J::InterpretMeasurement */
/***********************************************************************
* CTestITN_J::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_J::InterpretCurrency( const SPPHRASEPROPERTY *pProperties,
DOUBLE *pdblVal,
WCHAR *pszVal,
UINT cSize)
{
if ( !pdblVal || !pszVal )
{
return E_POINTER;
}
// Find the dollars and cents properties
const SPPHRASEPROPERTY *pPropDollars;
for ( pPropDollars = pProperties;
pPropDollars && ( YENs != pPropDollars->ulId );
pPropDollars = pPropDollars->pNextSibling )
;
SPDBG_ASSERT( pPropDollars );
const WCHAR *pszDollars = NULL;
DOUBLE dblDollars = 0;
if ( pPropDollars )
{
SPDBG_ASSERT( VT_R8 == pPropDollars->vValue.vt );
pszDollars = pPropDollars->pszValue;
dblDollars = pPropDollars->vValue.dblVal;
}
if (pPropDollars)
{
//Japanese people don't like \1Million, for the case of whole numbers like MANN, OKU,
//Simply write out the YEN at the end.
if (iswdigit( pszDollars[wcslen(pszDollars) - 1] ))
{
wcscpy(pszVal + 1, pszDollars);
pszVal[0] = L'\\';
}
else
{
wcscpy(pszVal, pszDollars);
wcscat(pszVal, L"\x5186");
}
}
*pdblVal = dblDollars;
return S_OK;
} /* CTestITN_J::InterpretCurrency */
/***********************************************************************
* CTestITN_J::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_J::AddPropertyAndReplacement( const WCHAR *szBuff,
const DOUBLE dblValue,
const ULONG ulMinPos,
const ULONG ulMaxPos,
const ULONG ulFirstElement,
const ULONG ulCountOfElements )
{
// 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 = SPAF_ONE_TRAILING_SPACE;
repl.pszReplacementText = szBuff;
repl.ulFirstElement = ulFirstElement;
repl.ulCountOfElements = ulCountOfElements;
hr = m_pSite->AddTextReplacement(&repl);
}
return hr;
} /* CTestITN_J::AddPropertyAndReplacement */
// Helper functions
/***********************************************************************
* CTestITN_J::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_J::MakeDisplayNumber( DOUBLE dblNum,
DWORD dwDisplayFlags,
UINT uiFixedWidth,
UINT uiDecimalPlaces,
WCHAR *pwszNum,
UINT cSize,
BOOL bForced)
{
SPDBG_ASSERT( pwszNum );
SPDBG_ASSERT( !SPIsBadWritePtr( pwszNum, cSize ) );
*pwszNum = 0;
// check for straight millions and straight billions
if (( dwDisplayFlags & DF_WHOLENUMBER ) && (dblNum > 0) && !bForced)
{
HRESULT hr;
if ( 0 == (( ((LONGLONG) dblNum) % CHUU )) )
{
// e.g. for "five billion" get the "5" and then
// tack on " billion"
hr = MakeDisplayNumber( ( dblNum / ((DOUBLE) CHUU) ),
dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pwszNum, cSize, FALSE );
if ( SUCCEEDED( hr ) )
{
//wcscat( pwszNum, L" " );
wcscat( pwszNum, CHUU_STR );
}
return hr;
}
else if (( ((LONGLONG) dblNum) < CHUU ) &&
( 0 == (( ((LONGLONG) dblNum) % OKU )) ))
{
hr = MakeDisplayNumber( ( dblNum / ((DOUBLE) OKU) ),
dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pwszNum, cSize, FALSE );
if ( SUCCEEDED( hr ) )
{
//wcscat( pwszNum, L" " );
wcscat( pwszNum, OKU_STR );
}
return hr;
}
else if (( ((LONGLONG) dblNum) < OKU ) &&
( 0 == (( ((LONGLONG) dblNum) % MANN )) ))
{
hr = MakeDisplayNumber( ( dblNum / ((DOUBLE) MANN) ),
dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pwszNum, cSize, FALSE );
if ( SUCCEEDED( hr ) )
{
//wcscat( pwszNum, L" " );
wcscat( pwszNum, MANN_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 < 1e16 );
WCHAR *pwszTemp = new WCHAR[ cSize ];
if ( !pwszTemp )
{
return E_OUTOFMEMORY;
}
*pwszTemp = 0;
LONGLONG llIntPart = (LONGLONG) dblNum;
UINT 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;
}
// 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.
GetNumberFormatDefaults();
// Make a copy so that we can change some fields according to the
// flags param
NUMBERFMT 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
TCHAR *pszFormattedNum = new TCHAR[ cSize ];
if ( !pszFormattedNum )
{
return E_OUTOFMEMORY;
}
*pszFormattedNum = 0;
USES_CONVERSION;
int iRet;
do
{
iRet = ::GetNumberFormat( LOCALE_USER_DEFAULT, 0,
W2T( pwszNum ), &nmfmt, pszFormattedNum, 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, T2W(pszFormattedNum) );
delete[] pszFormattedNum;
// 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_J::MakeDisplayNumber */
/***********************************************************************
* CTestITN_J::GetNumberFormatDefaults *
*-------------------------------------*
* Description:
* This finds all of the defaults for formatting numbers for
* this user.
*************************************************************************/
void CTestITN_J::GetNumberFormatDefaults()
{
LCID lcid = ::GetUserDefaultLCID();
TCHAR pszLocaleData[ MAX_LOCALE_DATA ];
::GetLocaleInfo( lcid, LOCALE_IDIGITS, pszLocaleData, MAX_LOCALE_DATA );
m_nmfmtDefault.NumDigits = _ttoi( pszLocaleData );
::GetLocaleInfo( lcid, LOCALE_ILZERO, pszLocaleData, MAX_LOCALE_DATA );
// It's always either 0 or 1
m_nmfmtDefault.LeadingZero = _ttoi( pszLocaleData );
::GetLocaleInfo( lcid, LOCALE_SGROUPING, pszLocaleData, MAX_LOCALE_DATA );
// It will look like single_digit;0, or else it will look like
// 3;2;0
UINT uiGrouping = *pszLocaleData - _T('0');
if ( (3 == uiGrouping) && (_T(';') == pszLocaleData[1]) && (_T('2') == pszLocaleData[2]) )
{
uiGrouping = 32;
}
m_nmfmtDefault.Grouping = uiGrouping;
::GetLocaleInfo( lcid, LOCALE_SDECIMAL, m_pszDecimalSep, MAX_LOCALE_DATA );
m_nmfmtDefault.lpDecimalSep = m_pszDecimalSep;
::GetLocaleInfo( lcid, LOCALE_STHOUSAND, m_pszThousandSep, MAX_LOCALE_DATA );
m_nmfmtDefault.lpThousandSep = m_pszThousandSep;
::GetLocaleInfo( lcid, LOCALE_INEGNUMBER, pszLocaleData, MAX_LOCALE_DATA );
m_nmfmtDefault.NegativeOrder = _ttoi( pszLocaleData );
} /* CTestITN_J::GetNumberFormatDefaults
/***********************************************************************
* 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 HandleDigitsAfterDecimal( WCHAR *pwszFormattedNum,
UINT cSizeOfFormattedNum,
const WCHAR *pwszRightOfDecimal )
{
SPDBG_ASSERT( pwszFormattedNum );
// First need to find what the decimal string is
LCID lcid = ::GetUserDefaultLCID();
TCHAR pszDecimalString[ 5 ]; // Guaranteed to be no longer than 4 long
int iRet = ::GetLocaleInfo( lcid, LOCALE_SDECIMAL, pszDecimalString, 4 );
SPDBG_ASSERT( iRet );
USES_CONVERSION;
WCHAR *pwcDecimal = wcsstr( pwszFormattedNum, T2W(pszDecimalString) );
SPDBG_ASSERT( pwcDecimal );
// pwcAfterDecimal points to the first character after the decimal separator
WCHAR *pwcAfterDecimal = pwcDecimal + _tcslen( pszDecimalString );
// 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;
} /* HandleDigitsAfterDecimal */
/***********************************************************************
* ComputeNum9999 *
*----------------*
* Description:
* Converts a set of SPPHRASEPROPERTYs into a number in
* [-9999, 9999].
* 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 ComputeNum9999(const SPPHRASEPROPERTY *pProperties )//, ULONG *pVal)
{
ULONG ulVal = 0;
for (const SPPHRASEPROPERTY * pProp = pProperties; pProp; pProp = pProp->pNextSibling)
{
if ( ZERO != pProp->ulId )
{
SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt );
ulVal += pProp->vValue.ulVal;
}
}
return ulVal;
} /* ComputeNum9999 */
/***********************************************************************
* GetMinAndMaxPos *
*-----------------*
* Description:
* Gets the minimum and maximum elements spanned by the
* set of properties
*************************************************************************/
void 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;
} /* GetMinAndMaxPos */
/***********************************************************************
* GetMonthName *
*--------------*
* Description:
* Gets the name of the month, abbreviated if desired
* Return:
* Number of characters written to pszMonth, 0 if failed
*************************************************************************/
int 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;
}
TCHAR *pszMonth = new TCHAR[ cSize ];
if ( !pszMonth )
{
return 0;
}
int iRet = ::GetLocaleInfo( LOCALE_USER_DEFAULT, lctype, pszMonth, cSize );
iRet = _mbslen((const unsigned char*) pszMonth); //Jpn needs chars, not bytes
USES_CONVERSION;
wcscpy( pwszMonth, T2W(pszMonth) );
delete[] pszMonth;
return iRet;
} /* GetMonthName */
/***********************************************************************
* GetDayOfWeekName *
*------------------*
* Description:
* Gets the name of the day of week, abbreviated if desired
* Return:
* Number of characters written to pszDayOfWeek, 0 if failed
*************************************************************************/
int GetDayOfWeekName( int iDayOfWeek, WCHAR *pwszDayOfWeek, int cSize, bool fAbbrev )
{
LCTYPE lctype;
switch ( iDayOfWeek )
{
case 0:
// Sunday is day 7
lctype = fAbbrev ? LOCALE_SABBREVDAYNAME7 : LOCALE_SDAYNAME7;
break;
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;
default:
return 0;
}
TCHAR *pszDayOfWeek = new TCHAR[ cSize ];
if ( !pszDayOfWeek )
{
return 0;
}
int iRet = ::GetLocaleInfo( LOCALE_USER_DEFAULT, lctype, pszDayOfWeek, cSize );
USES_CONVERSION;
wcscpy( pwszDayOfWeek, T2W(pszDayOfWeek) );
iRet = wcslen(pwszDayOfWeek);
delete[] pszDayOfWeek;
return iRet;
} /* GetMonthName */
/***********************************************************************
* 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 FormatDate( const SYSTEMTIME &stDate,
TCHAR *pszFormat,
WCHAR *pwszDate,
int cSize,
const WCHAR *pwszEmperor)
{
if ( !pszFormat || !pwszDate )
{
SPDBG_ASSERT( FALSE );
return 0;
}
WCHAR * const pwszDateStart = pwszDate;
// Convert the format string to unicode
WCHAR pwszFormat[ MAX_PATH ];
USES_CONVERSION;
wcscpy( pwszFormat, T2W(pszFormat) );
WCHAR *pwc = pwszFormat;
//Modify the format string to drop the fileds we don't have (Year, gg)
while ( *pwc )
{
switch( *pwc )
{
case L'y':
if (!stDate.wYear)
{
do
{
*pwc++ = L'\'';
} while ( *pwc && (L'M' != *pwc) && (L'd' != *pwc));
}
else
{
pwc++;
}
break;
case L'g':
*pwc++ = L'\'';
break;
default:
pwc ++;
break;
}
}
pwc = pwszFormat;
// output the Emperor's name if there is one
if (pwszEmperor)
{
wcscpy(pwszDate,pwszEmperor);
pwszDate += wcslen(pwszEmperor);
}
// Copy the format string to the date string character by
// character, replacing the string like "dddd" as appropriate
while ( *pwc )
{
switch( *pwc )
{
case L'\'':
pwc++; // Don't need '
break;
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 );
break;
default: // More than 4? Treat it as 4
// Day of week
iRet = GetDayOfWeekName( stDate.wDayOfWeek, pwszDate, cSize, false );
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 );
break;
default: // More than 4? Treat it as 4
// Month
iRet = GetMonthName( stDate.wMonth, pwszDate, cSize, false );
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 );
switch ( cNumYs )
{
case 1:
// Last two digits of year, width of 2
if (stDate.wYear % 100 > 9)
{
swprintf( pwszDate, L"%02d", stDate.wYear % 100 );
pwszDate += 2;
}
else
{
swprintf( pwszDate, L"%01d", stDate.wYear % 100 );
pwszDate += 1;
}
break;
case 2:
// Last two digits of year, width of 2
{
swprintf( pwszDate, L"%02d", stDate.wYear % 100 );
pwszDate += 2;
}
break;
default:
// 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;
}
case L'g':
{
// NB: GetCalendarInfo is supported on Win98 or Win2K, but not on NT4
/*
if ( L'g' == *(pwc + 1) )
{
// Get the era string
TCHAR pszCalEra[ MAX_LOCALE_DATA ];
if ( 0 == GetCalendarInfo( LOCALE_USER_DEFAULT,
CAL_GREGORIAN, CAL_SERASTRING, pszCalEra, MAX_LOCALE_DATA ) )
{
return 0;
}
USES_CONVERSION;
wcscpy( pwszDate, T2W(pszCalEra) );
pwc += 2;
}
else
{
// It's just a 'g'
*pwszDate++ = *pwc++;
}
*/
*pwszDate++ = *pwc++;
break;
}
default:
*pwszDate++ = *pwc++;
}
}
*pwszDate++ = 0;
return (pwszDate - pwszDateStart);
} /* FormatDate */