2018 lines
64 KiB
C++
2018 lines
64 KiB
C++
// 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 (<28><>)
|
||
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 <20><><EFBFBD>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 */
|