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