|
|
// TestITN.cpp : Implementation of CTestITN
#include "stdafx.h"
#include <winnls.h>
#include "Itngram.h"
#include "TestITN.h"
#include "sphelper.h"
#include "spddkhlp.h"
#include "test.h"
#define MILLION ((LONGLONG) 1000000)
#define BILLION ((LONGLONG) 1000000000)
#define MILLION_STR (L"million")
#define BILLION_STR (L"billion")
#define DAYOFWEEK_STR_ABBR (L"ddd")
#define DAYOFWEEK_STR (L"dddd")
#define NUM_US_STATES 57
#define NUM_CAN_PROVINCES 10
BOOL CALLBACK EnumCalendarInfoProc( LPTSTR lpCalendarInfoString ) { return FALSE; }
/////////////////////////////////////////////////////////////////////////////
// CTestITN
/****************************************************************************
* CTestITN::InitGrammar * *-----------------------* * Description: * Initialize a grammar that is loaded from an object (DLL). * - pszGrammarName name of the grammar to be loaded (in case this * object supports multiple grammars) * - pvGrammarData pointer to the serialized binary grammar * Returns: * S_OK * failure codes that are implementation specific ********************************************************************* RAL ***/
STDMETHODIMP CTestITN::InitGrammar(const WCHAR * pszGrammarName, const void ** pvGrammarData) { HRESULT hr = S_OK; HRSRC hResInfo = ::FindResource(_Module.GetModuleInstance(), _T("TEST"), _T("ITNGRAMMAR")); if (hResInfo) { HGLOBAL hData = ::LoadResource(_Module.GetModuleInstance(), hResInfo); if (hData) { *pvGrammarData = ::LockResource(hData); if (*pvGrammarData == NULL) { hr = HRESULT_FROM_WIN32(::GetLastError()); } } else { hr = HRESULT_FROM_WIN32(::GetLastError()); } } else { hr = HRESULT_FROM_WIN32(::GetLastError()); }
return hr; }
/****************************************************************************
* CTestITN::Interpret * *---------------------* * Description: * Given a phrase structure of the sub-tree spanned by this rule, given by * ulFristElement and ulCountOfElements and rule info (in pPhrase), examine * and generate new properties/text replacements which are set in the outer * phrase using pSite. * Returns: * S_OK * S_FALSE -- nothing was added/changed ********************************************************************* RAL ***/
STDMETHODIMP CTestITN::Interpret(ISpPhraseBuilder * pPhrase, const ULONG ulFirstElement, const ULONG ulCountOfElements, ISpCFGInterpreterSite * pSite) { HRESULT hr = S_OK; ULONG ulRuleId = 0; CSpPhrasePtr cpPhrase; hr = pPhrase->GetPhrase(&cpPhrase);
m_pSite = pSite;
// Get the minimum and maximum positions
ULONG ulMinPos; ULONG ulMaxPos; //Just use ulFirstElement & ulCountOfElements
//GetMinAndMaxPos( cpPhrase->pProperties, &ulMinPos, &ulMaxPos );
ulMinPos = ulFirstElement; ulMaxPos = ulMinPos + ulCountOfElements;
// Unless otherwise specified, this is the display attributes
BYTE bDisplayAttribs = SPAF_ONE_TRAILING_SPACE;
if (SUCCEEDED(hr)) { hr = E_FAIL;
WCHAR pszValueBuff[ MAX_PATH ]; // No ITN result should be longer than this
DOUBLE dblValue; // All ITN results will have a 64-bit value
pszValueBuff[0] = 0;
switch (cpPhrase->Rule.ulId) { case GRID_INTEGER_STANDALONE: // Fired as a toplevel rule
hr = InterpretNumber( cpPhrase->pProperties, true, &dblValue, pszValueBuff, MAX_PATH, true );
if (SUCCEEDED( hr ) && ( dblValue >= 0 ) && ( dblValue <= 20 ) && ( GRID_DIGIT_NUMBER != cpPhrase->pProperties->ulId ) && ( GRID_INTEGER_MILLBILL != cpPhrase->pProperties->ulId )) { // Throw this one out because numbers like "three"
// shouldn't be ITNed by themselves
hr = E_FAIL; } break; case GRID_INTEGER: case GRID_INTEGER_NONNEG: case GRID_INTEGER_99: case GRID_INTEGER_999: case GRID_ORDINAL: case GRID_MINSEC: case GRID_CLOCK_MINSEC: // Number
hr = InterpretNumber( cpPhrase->pProperties, true, &dblValue, pszValueBuff, MAX_PATH ); break;
// Number "spelled out" digit by digit
case GRID_DIGIT_NUMBER: case GRID_YEAR: case GRID_CENTS: hr = InterpretDigitNumber( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_FP_NUMBER: case GRID_FP_NUMBER_NONNEG: hr = InterpretFPNumber( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_INTEGER_MILLBILL: hr = InterpretMillBill( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_DENOMINATOR: case GRID_DENOMINATOR_SINGULAR: hr = InterpretNumber( cpPhrase->pProperties, true, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_FRACTION: hr = InterpretFraction( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_DATE: hr = InterpretDate( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_TIME: hr = InterpretTime( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break; case GRID_STATEZIP: hr = InterpretStateZip( cpPhrase->pProperties, pszValueBuff, MAX_PATH, &bDisplayAttribs ); break;
case GRID_ZIPCODE: case GRID_ZIP_PLUS_FOUR: hr = (MakeDigitString( cpPhrase->pProperties, pszValueBuff, MAX_PATH ) > 0) ? S_OK : E_FAIL; break;
case GRID_CAN_ZIPCODE: hr = InterpretCanadaZip( cpPhrase->pProperties, pszValueBuff, MAX_PATH ); break;
case GRID_PHONE_NUMBER: hr = InterpretPhoneNumber( cpPhrase->pProperties, pszValueBuff, MAX_PATH ); break;
case GRID_DEGREES: hr = InterpretDegrees( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_MEASUREMENT: hr = InterpretMeasurement( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break;
case GRID_CURRENCY: // Currency
hr = InterpretCurrency( cpPhrase->pProperties, &dblValue, pszValueBuff, MAX_PATH ); break; default: _ASSERT(FALSE); break; }
if ( SUCCEEDED( hr ) ) { hr = AddPropertyAndReplacement( pszValueBuff, dblValue, ulMinPos, ulMaxPos, ulMinPos, ulMaxPos - ulMinPos, bDisplayAttribs );
return hr; }
} // Nothing was ITNed
return S_FALSE; }
/***********************************************************************
* CTestITN::InterpretNumber * *---------------------------* * Description: * Interprets a number in the range -999,999,999,999 to * +999,999,999,999 and sends the properties and * replacements to the CFGInterpreterSite as appropriate. * The property will be added and the pszValue will be a string * with the correct display number. * If fCardinal is set, makes the display a cardinal number; * otherwise makes it an ordinal number. * The number will be formatted only if it was a properly-formed * number (not given digit by digit). * Result: *************************************************************************/ HRESULT CTestITN::InterpretNumber(const SPPHRASEPROPERTY *pProperties, const bool fCardinal, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize, const bool fFinalDisplayFmt) { if ( !pdblVal || !pszVal ) { return E_POINTER; }
*pszVal = 0;
LONGLONG llValue = 0; int iPositive = 1;
const SPPHRASEPROPERTY *pFirstProp = pProperties;
// Handle negatives
if ( NEGATIVE == pFirstProp->ulId ) { // There's no such thing as a negative ordinal
SPDBG_ASSERT( fCardinal );
// There had better be more stuff following
SPDBG_ASSERT( pFirstProp->pNextSibling );
iPositive = -1;
pFirstProp = pFirstProp->pNextSibling; }
// Handle the "2.6 million" case, in which case the number
// has already been formatted
if ( GRID_INTEGER_MILLBILL == pFirstProp->ulId ) { if ( cSize < (wcslen( pFirstProp->pszValue ) + 1) ) { return E_INVALIDARG; } *pdblVal = pFirstProp->vValue.dblVal * iPositive; if ( iPositive < 0 ) { wcscpy( pszVal, m_pwszNeg ); } wcscat( pszVal, pFirstProp->pszValue );
return S_OK; }
// Handle the digit-by-digit case
if ( GRID_DIGIT_NUMBER == pFirstProp->ulId ) { // There had better be nothing following this
SPDBG_ASSERT( !pFirstProp->pNextSibling ); SPDBG_ASSERT( VT_R8 == pFirstProp->vValue.vt ); SPDBG_ASSERT( pFirstProp->pszValue );
DOUBLE dblVal = pFirstProp->vValue.dblVal; *pdblVal = dblVal * iPositive; // Just get the string and make it negative if necessary
if ( cSize < wcslen( pFirstProp->pszValue ) ) { return E_INVALIDARG; } wcscpy( pszVal, pFirstProp->pszValue ); if ( iPositive < 0 ) { MakeNumberNegative( pszVal ); }
return S_OK; }
for (const SPPHRASEPROPERTY * pProp = pFirstProp; pProp; pProp ? pProp = pProp->pNextSibling : NULL) { switch(pProp->ulId) { case ONES: { SPDBG_ASSERT(pProp->pFirstChild); llValue += ComputeNum999( pProp->pFirstChild ); } break; case THOUSANDS: { llValue += ComputeNum999( pProp->pFirstChild ) * 1000; } break; case MILLIONS: { SPDBG_ASSERT(pProp->pFirstChild); llValue += ComputeNum999( pProp->pFirstChild ) * (LONGLONG) 1e6; } break; case BILLIONS: { SPDBG_ASSERT(pProp->pFirstChild); llValue += ComputeNum999( pProp->pFirstChild ) * (LONGLONG) 1e9; } break; case HUNDREDS: { SPDBG_ASSERT( pProp->pFirstChild ); llValue += ComputeNum999( pProp->pFirstChild ) * 100; } break;
case TENS: default: SPDBG_ASSERT(false); } }
llValue *= iPositive;
*pdblVal = (DOUBLE) llValue;
DWORD dwDisplayFlags = DF_WHOLENUMBER | (fCardinal ? 0 : DF_ORDINAL) | (fFinalDisplayFmt ? DF_MILLIONBILLION : 0 ); return MakeDisplayNumber( *pdblVal, dwDisplayFlags, 0, 0, pszVal, cSize );
} /* CTestITN::InterpretNumber */
/***********************************************************************
* CTestITN::InterpretDigitNumber * *--------------------------------* * Description: * Interprets an integer in (-INF, +INF) that has been spelled * out digit by digit. * Also handles years. * Result: *************************************************************************/ HRESULT CTestITN::InterpretDigitNumber( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize) { if ( !pdblVal || !pszVal ) { return E_POINTER; }
BOOL fPositive = TRUE; ULONG ulLength = 0; *pszVal = 0; WCHAR *pwc = pszVal; UINT cLen = 0; for (const SPPHRASEPROPERTY * pProp = pProperties; pProp && (cLen < cSize); pProp ? pProp = pProp->pNextSibling : NULL) { switch(pProp->ulId) { /*
case NEGATIVE: { SPDBG_ASSERT( pProp == pProperties );
fPositive = FALSE; *pwc++ = L'-'; cLen++; break; } */ case DIGIT: { *pwc++ = pProp->pszValue[0]; cLen++; break; } case TWODIGIT: { SPDBG_ASSERT( pProp->pFirstChild ); ULONG ulTwoDigit = ComputeNum999( pProp->pFirstChild ); SPDBG_ASSERT( ulTwoDigit < 100 );
// Get each digit
*pwc++ = L'0' + ((UINT) ulTwoDigit) / 10; *pwc++ = L'0' + ((UINT) ulTwoDigit) % 10;
cLen += 2;
break; } case ONEDIGIT: { SPDBG_ASSERT( pProp->pFirstChild); *pwc++ = pProp->pFirstChild->pszValue[0]; cLen++;
break; } case TWOTHOUSAND: { // Handles the "two thousand" in dates
if ( pProp->pNextSibling ) { if ( TWODIGIT == pProp->pNextSibling->ulId ) { wcscpy( pwc, L"20" ); pwc += 2; cLen += 2; } else { SPDBG_ASSERT( ONEDIGIT == pProp->pNextSibling->ulId ); wcscpy( pwc, L"200" ); pwc += 3; cLen += 3; } } else { wcscpy( pwc, L"2000" ); pwc += 4; cLen += 4; } break; } case DATE_HUNDREDS: { SPDBG_ASSERT( pProp->pFirstChild ); DOUBLE dblTwoDigit; HRESULT hr = InterpretDigitNumber( pProp->pFirstChild, &dblTwoDigit, pwc, cSize - cLen ); if ( FAILED( hr ) ) { return hr; }
pwc += 2; *pwc++ = L'0'; *pwc++ = L'0'; cLen += 4;
break; }
default: SPDBG_ASSERT(false); } } *pwc = 0;
if ( cLen <= MAX_SIG_FIGS ) { *pdblVal = (DOUBLE) _wtoi64( pszVal ); } else { // Just make sure it's not zero so denominators don't fail
*pdblVal = 1; }
return S_OK; } /* CTestITN::InterpretDigitNumber */
/***********************************************************************
* CTestITN::InterpretFPNumber * *-----------------------------* * Description: * Interprets a floating-point number of arbitrarily many * sig figs. (The value in *pdblVal will be truncated * as necessary to fit into a DOUBLE.) * The properties will look like an optional NEGATIVE property * followed by an optional ONES property, * which has already been appropriately ITNed, * followed by the stuff to the right of the decimal place ************************************************************************/ HRESULT CTestITN::InterpretFPNumber( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize ) { if ( !pdblVal || !pszVal ) { return E_POINTER; } SPDBG_ASSERT( pProperties );
*pdblVal = 0; *pszVal = 0;
bool fNonNegative = true; const SPPHRASEPROPERTY *pPropertiesFpPart = NULL; const SPPHRASEPROPERTY *pPropertiesPointZero = NULL; const SPPHRASEPROPERTY *pPropertiesOnes = NULL; const SPPHRASEPROPERTY *pPropertiesZero = NULL; const SPPHRASEPROPERTY *pPropertiesNegative = NULL; const SPPHRASEPROPERTY *pPropertiesPtr;
for(pPropertiesPtr=pProperties; pPropertiesPtr; pPropertiesPtr=pPropertiesPtr->pNextSibling) { if (POINT_ZERO == pPropertiesPtr->ulId ) pPropertiesPointZero = pPropertiesPtr; else if ( FP_PART == pPropertiesPtr->ulId ) pPropertiesFpPart = pPropertiesPtr; else if (ONES == pPropertiesPtr->ulId ) pPropertiesOnes = pPropertiesPtr; else if (ZERO == pPropertiesPtr->ulId ) pPropertiesZero = pPropertiesPtr; else if (NEGATIVE == pPropertiesPtr->ulId ) pPropertiesNegative = pPropertiesPtr; }
// Get current number formatting defaults
HRESULT hr = GetNumberFormatDefaults(); if ( FAILED( hr ) ) { return hr; }
// Look for negative sign
if ( pPropertiesNegative ) { fNonNegative = false; }
// Look for optional ONES (optional because you can say
// "point five"
if ( pPropertiesOnes ) { // This property has already been ITNed correctly,
// so just copy in the text
if ( (cSize - wcslen( pszVal )) < (wcslen( pPropertiesOnes->pszValue ) + 1) ) { return E_INVALIDARG; } wcscpy( pszVal, pPropertiesOnes->pszValue );
// Get the value
*pdblVal = pPropertiesOnes->vValue.dblVal;
} else if (pPropertiesZero || m_nmfmtDefault.LeadingZero ) { // There should be a leading zero
wcscpy( pszVal, L"0" ); }
SPDBG_ASSERT(pPropertiesFpPart || pPropertiesPointZero);
// Put in a decimal separator
if ( (cSize - wcslen( pszVal )) < (wcslen( m_nmfmtDefault.lpDecimalSep ) + 1) ) { return E_INVALIDARG; } wcscat( pszVal, m_nmfmtDefault.lpDecimalSep );
if ( pPropertiesFpPart ) { // Deal with the FP part, which will also have been ITNed correctly
if ( (cSize - wcslen( pszVal )) < (wcslen( pPropertiesFpPart->pszValue ) + 1) ) { return E_INVALIDARG; } wcscat( pszVal, pPropertiesFpPart->pszValue ); // Get the correct value
DOUBLE dblFPPart = pPropertiesFpPart->vValue.dblVal; for ( UINT ui=0; ui < wcslen( pPropertiesFpPart->pszValue ); ui++ ) { dblFPPart /= (DOUBLE) 10; } *pdblVal += dblFPPart; } else { // "point oh": The DOUBLE is already right, just add a "0"
if ( (cSize - wcslen( pszVal )) < 2 ) { return E_INVALIDARG; } wcscat( pszVal, L"0" ); }
// Handle the negative sign
if ( !fNonNegative ) { *pdblVal = -*pdblVal;
if ( (cSize = wcslen( pszVal )) < 2 ) { return E_INVALIDARG; } HRESULT hr = MakeNumberNegative( pszVal ); if ( FAILED( hr ) ) { return hr; } }
return S_OK; } /* CTestITN::InterpretFPNumber */
/***********************************************************************
* CTestITN::InterpretMillBill * *-----------------------------* * Description: * Interprets a number that needs to be displayed with * the word "million" or "billion" in the display format. *************************************************************************/ HRESULT CTestITN::InterpretMillBill( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize ) { const SPPHRASEPROPERTY *pPropertiesPtr; if ( !pdblVal || !pszVal ) { return E_POINTER; } SPDBG_ASSERT( pProperties ); HRESULT hr = GetNumberFormatDefaults(); if ( FAILED( hr ) ) { return hr; }
*pszVal = 0;
// Handle optional negative sign
bool fNonNegative = true; if ( NEGATIVE == pProperties->ulId ) { // Always do '-', regardless of control panel option settings
if ( cSize < 2 ) { return E_INVALIDARG; } wcscpy( pszVal, m_pwszNeg );
cSize -= 1; pProperties = pProperties->pNextSibling; }
// Handle the number part
SPDBG_ASSERT( pProperties ); for( pPropertiesPtr = pProperties; pPropertiesPtr && ( GRID_INTEGER_99 != pPropertiesPtr->ulId ) && ( GRID_FP_NUMBER_NONNEG != pPropertiesPtr->ulId ); pPropertiesPtr = pPropertiesPtr->pNextSibling); SPDBG_ASSERT(( GRID_INTEGER_99 == pPropertiesPtr->ulId ) || ( GRID_FP_NUMBER_NONNEG == pPropertiesPtr->ulId )); *pdblVal = pPropertiesPtr->vValue.dblVal; if ( cSize < (wcslen( pPropertiesPtr->pszValue ) + 1) ) { return E_INVALIDARG; } wcscat( pszVal, pPropertiesPtr->pszValue ); cSize -= wcslen( pPropertiesPtr->pszValue );
// Handle the "millions" part
SPDBG_ASSERT( pProperties ); for( pPropertiesPtr = pProperties; pPropertiesPtr && ( MILLBILL != pPropertiesPtr->ulId ); pPropertiesPtr = pPropertiesPtr->pNextSibling); SPDBG_ASSERT( MILLBILL == pPropertiesPtr->ulId ); *pdblVal *= ( (MILLIONS == pPropertiesPtr->vValue.uiVal) ? MILLION : BILLION ); if ( cSize < (wcslen( pPropertiesPtr->pszValue ) + 2) ) { return E_INVALIDARG; } wcscat( pszVal, L" " ); wcscat( pszVal, pPropertiesPtr->pszValue );
if ( !fNonNegative ) { *pdblVal = -*pdblVal; }
return S_OK; } /* CTestITN::InterpretMillBill */
/***********************************************************************
* CTestITN::InterpretFraction * *-----------------------------* * Description: * Interprets a fraction. * The DENOMINATOR property should be present. * If the NUMERATOR property is absent, it is assumed to be 1. * Divides the NUMERATOR by the DENOMINATOR and sets the value * accordingly. *************************************************************************/ HRESULT CTestITN::InterpretFraction( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize) { if ( !pdblVal || !pszVal ) { return E_POINTER; } SPDBG_ASSERT( pProperties ); DOUBLE dblWholeValue = 0; DOUBLE dblFracValue = 1;
const SPPHRASEPROPERTY *pProp = pProperties; *pszVal = 0;
// Space to store whatever characters follow the digits
// in the numerator (like ")")
WCHAR pszTemp[ 10 ]; // will never need this much
pszTemp[0] = 0;
// Whole part is optional, otherwise assumed to be 0
if ( WHOLE == pProp->ulId ) { SPDBG_ASSERT( VT_R8 == pProp->vValue.vt ); dblWholeValue = pProp->vValue.dblVal; wcscpy( pszVal, pProp->pszValue );
// Keep track of anything that follows the digits
WCHAR *pwc; for ( pwc = pszVal + wcslen(pszVal) - 1; !iswdigit( *pwc ); pwc-- ) ; wcscpy( pszTemp, pwc + 1 ); *(pwc + 1) = 0;
// Add a space between the whole part and the fractional part
wcscat( pszVal, L" " );
SPDBG_ASSERT( pProp->pNextSibling ); pProp = pProp->pNextSibling; }
// Numerator is optional, otherwise assumed to be 1
if ( NUMERATOR == pProp->ulId ) { SPDBG_ASSERT( VT_R8 == pProp->vValue.vt ); dblFracValue = pProp->vValue.dblVal; wcscat( pszVal, pProp->pszValue );
// Find the last digit and copy everything after it
WCHAR *pwc; for ( pwc = pszVal + wcslen(pszVal) - 1; !iswdigit( *pwc ); pwc-- ) ; wcscat( pszTemp, pwc + 1 ); *(pwc + 1) = 0;
SPDBG_ASSERT( pProp->pNextSibling ); pProp = pProp->pNextSibling; } else if ( ZERO == pProp->ulId ) { dblFracValue = 0; wcscat( pszVal, L"0" );
SPDBG_ASSERT( pProp->pNextSibling ); pProp = pProp->pNextSibling; } else { // No numerator, assume 1
wcscat( pszVal, L"1" ); }
wcscat( pszVal, L"/" );
SPDBG_ASSERT( DENOMINATOR == pProp->ulId ); SPDBG_ASSERT( VT_R8 == pProp->vValue.vt ); if ( 0 == pProp->vValue.dblVal ) { // Will not ITN a fraction with a zero denominator
return E_FAIL; }
dblFracValue /= pProp->vValue.dblVal; wcscat( pszVal, pProp->pszValue );
// In case the denominator was an ordinal, strip off the "th" at the end
SPDBG_ASSERT( wcslen( pszVal ) ); WCHAR *pwc = pszVal + wcslen( pszVal ) - 1; for ( ; (pwc >= pszVal) && !iswdigit( *pwc ); pwc-- ) ; SPDBG_ASSERT( pwc > pszVal ); *(pwc + 1) = 0;
// Tack on the ")" or "-" from the end of the numerator
wcscat( pszVal, pszTemp );
*pdblVal = dblWholeValue + dblFracValue; return S_OK; } /* CTestITN::InterpretFraction */
/***********************************************************************
* CTestITN::InterpretDate * *-------------------------* * Description: * Interprets a date. * Converts the date into a VT_DATE format, even though it * gets stored as a VT_R8 (both are 64-bit quantities). * In case the date is not a valid date ("May fortieth nineteen * ninety nine") will add the properties for any numbers in there * and return S_FALSE. *************************************************************************/ HRESULT CTestITN::InterpretDate( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize) { if ( !pdblVal || !pszVal ) { return E_POINTER; }
*pszVal = 0;
// Get the date formatting string to be used right now
WCHAR pwszLongDateFormat[ MAX_DATE_FORMAT ]; if ( 0 == m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SLONGDATE, pwszLongDateFormat, MAX_DATE_FORMAT ) ) { return E_FAIL; } SYSTEMTIME stDate; memset( (void *) &stDate, 0, sizeof( stDate ) );
// Arguments for FormatDate()
WCHAR *pwszFormatArg = pwszLongDateFormat; WCHAR pwszFormat[ MAX_DATE_FORMAT ]; *pwszFormat = 0; bool fYear = false; // Necessary since year can be 0
bool fClearlyIntentionalYear = false; // "zero one"
bool fMonthFirst = true; // August 2000 as opposed to 2000 August
const SPPHRASEPROPERTY *pProp;
if (( MONTHYEAR == pProperties->ulId ) || ( YEARMONTH == pProperties->ulId )) { fMonthFirst = ( MONTHYEAR == pProperties->ulId ); // Look for the year and month properties underneath this one
pProperties = pProperties->pFirstChild; }
for ( pProp = pProperties; pProp; pProp = pProp->pNextSibling ) { switch ( pProp->ulId ) { case DAY_OF_WEEK: { SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt ); SPDBG_ASSERT( (0 < pProp->vValue.ulVal) && (7 >= pProp->vValue.ulVal) ); if ( (pProp->vValue.ulVal <= 0) || (pProp->vValue.ulVal > 7) ) { return E_INVALIDARG; } stDate.wDayOfWeek = (WORD) pProp->vValue.ulVal;
// Use the full long date format
pwszFormatArg = pwszLongDateFormat; } break;
case DAY_OF_MONTH: SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt ); SPDBG_ASSERT( (1 <= pProp->vValue.uiVal) && (31 >= pProp->vValue.uiVal) ); stDate.wDay = pProp->vValue.uiVal; break;
case MONTH: SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt ); SPDBG_ASSERT( (1 <= pProp->vValue.ulVal) && (12 >= pProp->vValue.ulVal) ); if ( (pProp->vValue.ulVal < 1) || (pProp->vValue.ulVal > 12) ) { return E_INVALIDARG; } stDate.wMonth = (WORD) pProp->vValue.ulVal; break;
case YEAR: // Year will have been ITNed already
SPDBG_ASSERT( VT_R8 == pProp->vValue.vt ); stDate.wYear = (WORD) pProp->vValue.dblVal;
fYear = true; if (( stDate.wYear < 10 ) && ( wcslen( pProp->pszValue ) >=2 )) { // Want to make sure "June zero one" does not
// become June 1 below
fClearlyIntentionalYear = true; }
break;
default: SPDBG_ASSERT( false ); break; } }
// Make sure that grammar gave us something valid
SPDBG_ASSERT( stDate.wMonth && ( stDate.wDayOfWeek ? stDate.wDay : 1 ) );
// Ambiguity workaround: Want to make sure that June 28 is June 28 and not June, '28
if ( fYear && !fClearlyIntentionalYear && stDate.wMonth && !stDate.wDay && (stDate.wYear >= 1) && (stDate.wYear <= 31) ) { fYear = false; stDate.wDay = stDate.wYear; stDate.wYear = 0; }
// Deal with the possible types of input
if ( stDate.wDay ) { if ( fYear ) { if ( !stDate.wDayOfWeek ) { // Remove the day of week from the current date format string.
// This format picture can be DAYOFWEEK_STR or DAYOFWEEK_STR_ABBR.
// This is in a loop since a pathological string could have
// more than one instance of the day of week...
WCHAR *pwc = NULL; do { pwc = wcsstr( pwszLongDateFormat, DAYOFWEEK_STR ); WCHAR pwszDayOfWeekStr[ MAX_LOCALE_DATA]; if ( pwc ) { wcscpy( pwszDayOfWeekStr, DAYOFWEEK_STR ); } else if ( NULL != (pwc = wcsstr( pwszLongDateFormat, DAYOFWEEK_STR_ABBR )) ) { wcscpy( pwszDayOfWeekStr, DAYOFWEEK_STR_ABBR ); }
if ( pwc ) { // A day-of-week string was found
// Copy over everything until this character info the format string
wcsncpy( pwszFormat, pwszLongDateFormat, (pwc - pwszLongDateFormat) ); pwszFormat[pwc - pwszLongDateFormat] = 0; // Skip over the day of week until the next symbol
// (the way date format strings work, this is the first
// alphabetical symbol
pwc += wcslen( pwszDayOfWeekStr ); while ( *pwc && (!iswalpha( *pwc ) || (L'd' == *pwc)) ) { pwc++; }
// Copy over everything from here on out
wcscat( pwszFormat, pwc );
pwszFormatArg = pwszFormat;
// Copy over so that we can find the next day-of-week string
wcscpy( pwszLongDateFormat, pwszFormat ); }
} while ( pwc ); } else { // The user did say the day of week
if ( !wcsstr( pwszLongDateFormat, DAYOFWEEK_STR_ABBR ) ) { // The format string does not called for the day of week
// being displayed anywhere
// In this case our best bet is to write out the day of week at
// the beginning of the output
wcscpy( pwszFormat, L"dddd, " ); wcscat( pwszFormat, pwszLongDateFormat );
pwszFormatArg = pwszFormat; } } } else // fYear == 0
{ // Just a month and a day
const SPPHRASEPROPERTY *pWhichComesFirst = pProperties; if ( stDate.wDayOfWeek ) { wcscpy( pwszFormat, L"dddd, " ); pWhichComesFirst = pWhichComesFirst->pNextSibling; } if ( MONTH == pWhichComesFirst->ulId ) { wcscat( pwszFormat, L"MMMM d" ); } else { wcscat( pwszFormat, L"d MMMM" ); }
pwszFormatArg = pwszFormat; } } else // stDate.wDay == 0
{ // Month, year format
if ( fMonthFirst ) { wcscpy( pwszFormat, L"MMMM, yyyy" ); } else { wcscpy( pwszFormat, L"yyyy MMMM" ); }
pwszFormatArg = pwszFormat; }
// Get the date in VariantTime form so we can set it as a semantic value
int iRet = ::SystemTimeToVariantTime( &stDate, pdblVal ); if ( 0 == iRet ) { // Not serious, just the semantic value will be wrong
*pdblVal = 0; }
// Do the formatting
iRet = FormatDate( stDate, pwszFormatArg, pszVal, cSize ); if ( 0 == iRet ) { return E_FAIL; }
return S_OK; } /* CTestITN::InterpretDate */
/***********************************************************************
* CTestITN::InterpretTime * *--------------------------* * Description: * Interprets time, which can be of the following forms: * * Hour with qualifier ("half past three"), time marker optional * * Hour with minutes, time marker mandatory * * Military time "hundred hours" * * Hour with "o'clock", time marker optional * * Number of hours and number of minutes and optional number * of seconds * Return: * S_OK * E_POINTER if !pdblVal or !pszVal *************************************************************************/ HRESULT CTestITN::InterpretTime( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize ) { if ( !pdblVal || !pszVal ) { return E_POINTER; } // Time marker and seconds should not be shown unless some
// component of the time specifically requires it
DWORD dwFlags = TIME_NOSECONDS | TIME_NOTIMEMARKER; SYSTEMTIME stTime; ::memset( (void *) &stTime, 0, sizeof( SYSTEMTIME ) );
bool fQuarterTo = false; bool fClockTime = true; bool fAMPM = false; UINT uAMPM = AM;
const SPPHRASEPROPERTY *pProp; for ( pProp = pProperties; pProp; pProp = pProp->pNextSibling ) { switch ( pProp->ulId ) { case HOUR_CLOCK: { UINT uiHour = pProp->vValue.uiVal; SPDBG_ASSERT(( uiHour > 0 ) && ( uiHour <= 12)); if ( fQuarterTo ) { // Push the hour back by one
// (push back to 12 if it's one)
uiHour = (1 == uiHour) ? 12 : (uiHour - 1); }
if ( 12 == uiHour ) { // The functions below are expecting "0" to indicate midnight
uiHour = 0; }
stTime.wHour = (WORD) uiHour; break; } case HOUR_COUNT: // Just take the hour for what it is
stTime.wHour = (WORD) pProp->vValue.dblVal;
// This is not a clock time
fClockTime = false; break;
case MINUTE: { // Minutes are evaluted as numbers, so their values
// are stored as doubles
stTime.wMinute = (WORD) pProp->vValue.dblVal; break; } case SECOND: { stTime.wSecond = (WORD) pProp->vValue.dblVal; dwFlags &= ~TIME_NOSECONDS; break; } case CLOCKTIME_QUALIFIER: { switch( pProp->vValue.uiVal ) { case QUARTER_TO: { fQuarterTo = true; stTime.wMinute = 45; break; } case QUARTER_PAST: { stTime.wMinute = 15; break; } case HALF_PAST: { stTime.wMinute = 30; break; } default: SPDBG_ASSERT( false ); }
break; } case AMPM: { // We don't know where it might arrive any more, so simple keep this information
fAMPM = true; uAMPM = pProp->vValue.uiVal; break; } default: SPDBG_ASSERT( false ); } } if (fAMPM) { SPDBG_ASSERT(( stTime.wHour >= 0 ) && ( stTime.wHour <= 11 )); if ( PM == uAMPM ) { stTime.wHour += 12; } dwFlags &= ~TIME_NOTIMEMARKER; }
HRESULT hr = S_OK; if ( fClockTime ) { // Get the time in VariantTime form so we can set it as a semantic value
if ( 0 == ::SystemTimeToVariantTime( &stTime, pdblVal ) ) { // Not serious, just the semantic value will be wrong
*pdblVal = 0; }
// Let the system format the time
int iRet = m_Unicode.GetTimeFormat( LOCALE_USER_DEFAULT, dwFlags, &stTime, NULL, pszVal, cSize );
// NB: GetTimeFormat() will put an extra space at the end of the
// time if the default format has AM or PM but TIME_NOTIMEMARKER is
// set
if ( iRet && (TIME_NOTIMEMARKER & dwFlags) ) { WCHAR *pwc = pszVal + wcslen( pszVal ) - 1; while ( iswspace( *pwc ) ) { *pwc-- = 0; } } hr = iRet ? S_OK : E_FAIL; } else { // No need to go through the system's time formatter
if ( cSize < 10 ) // Space for xxx:xx:xx\0
{ return E_INVALIDARG; }
if ( dwFlags & TIME_NOSECONDS ) { swprintf( pszVal, L"%d:%02d", stTime.wHour, stTime.wMinute ); } else { swprintf( pszVal, L"%d:%02d:%02d", stTime.wHour, stTime.wMinute, stTime.wSecond ); } }
return hr; } /* CTestITN::InterpretTime */
/***********************************************************************
* CTestITN::InterpretStateZip * *-----------------------------* * Description: * A StateZip must be a state name followed by a ZIP code. * There is no reasonable semantic value to attach to this ITN. * Return: * S_OK * E_POINTER if !pszVal * E_INVALIDARG if cSize is too small *************************************************************************/ HRESULT CTestITN::InterpretStateZip( const SPPHRASEPROPERTY *pProperties, WCHAR *pszVal, UINT cSize, BYTE *pbAttribs ) { if ( !pszVal || !pProperties || !pbAttribs ) { return E_POINTER; } if ( cSize < MAX_STATEZIP ) { return E_INVALIDARG; }
const SPPHRASEPROPERTY *pPropertiesComma = NULL; const SPPHRASEPROPERTY *pPropertiesState = NULL; const SPPHRASEPROPERTY *pPropertiesZipCode = NULL; const SPPHRASEPROPERTY *pPropertiesZipCodeExtra = NULL; const SPPHRASEPROPERTY *pPropertiesPtr;
for(pPropertiesPtr=pProperties; pPropertiesPtr; pPropertiesPtr=pPropertiesPtr->pNextSibling) { if (COMMA == pPropertiesPtr->ulId ) pPropertiesComma = pPropertiesPtr; else if ( (US_STATE == pPropertiesPtr->ulId ) || (CAN_PROVINCE == pPropertiesPtr->ulId )) pPropertiesState = pPropertiesPtr; else if (ZIPCODE == pPropertiesPtr->ulId ) pPropertiesZipCode = pPropertiesPtr; else if (FOURDIGITS == pPropertiesPtr->ulId ) pPropertiesZipCodeExtra = pPropertiesPtr; }
// Comma after the city name if a comma was spoken
if ( pPropertiesComma ) { // Will want to consume leading spaces when this is displayed
*pbAttribs |= SPAF_CONSUME_LEADING_SPACES;
wcscpy( pszVal, L", " ); }
// Get the state name
SPDBG_ASSERT( pPropertiesState ); UINT uiState = pPropertiesState->vValue.uiVal; if ( US_STATE == pPropertiesState->ulId ) { SPDBG_ASSERT( uiState < NUM_US_STATES ); wcscat( pszVal, pPropertiesState->pszValue ); } else if ( CAN_PROVINCE == pPropertiesState->ulId ) { SPDBG_ASSERT( uiState < NUM_CAN_PROVINCES ); wcscat( pszVal, pPropertiesState->pszValue ); } else { SPDBG_ASSERT( false ); } wcscat( pszVal, L" " );
// Get the ZIP
SPDBG_ASSERT( pPropertiesZipCode ); wcscat( pszVal, pPropertiesZipCode->pszValue );
// Get the ZIP+4 if it's there
if ( pPropertiesZipCodeExtra ) { wcscat( pszVal, L"-" ); wcscat( pszVal, pPropertiesZipCodeExtra->pszValue ); }
return S_OK; } /* CTestITN::InterpretStateZip */
/***********************************************************************
* CTestITN::InterpretCanadaZip * *------------------------------* * Description: * A CanadaZip must be Alpha/Num/Alpha Num/Alpha/Num * There is no reasonable semantic value to attach to this ITN. * Return: * S_OK * E_POINTER if !pszVal * E_INVALIDARG if cSize is too small *************************************************************************/ HRESULT CTestITN::InterpretCanadaZip( const SPPHRASEPROPERTY *pProperties, WCHAR *pszVal, UINT cSize ) { if ( !pszVal ) { return E_POINTER; } if ( cSize < CANADIAN_ZIPSIZE ) { return E_INVALIDARG; }
int i; for ( i=0; i < 3; i++, pProperties = pProperties->pNextSibling ) { SPDBG_ASSERT( pProperties ); wcscat( pszVal, pProperties->pszValue ); } wcscat( pszVal, L" " ); for ( i=0; i < 3; i++, pProperties = pProperties->pNextSibling ) { SPDBG_ASSERT( pProperties ); wcscat( pszVal, pProperties->pszValue ); } return S_OK; } /* CTestITN::InterpretStateZip */
/***********************************************************************
* CTestITN::InterpretPhoneNumber * *--------------------------------* * Description: * Phone number * Return: * S_OK * E_POINTER if !pszVal * E_INVALIDARG if cSize is too small *************************************************************************/ HRESULT CTestITN::InterpretPhoneNumber( const SPPHRASEPROPERTY *pProperties, WCHAR *pszVal, UINT cSize ) { if ( !pProperties || !pszVal ) { return E_POINTER; }
if ( cSize < MAX_PHONE_NUMBER ) { return E_INVALIDARG; }
pszVal[0] = 0;
if ( ONE_PLUS == pProperties->ulId ) { SPDBG_ASSERT( pProperties->pNextSibling && (AREA_CODE == pProperties->pNextSibling->ulId) ); wcscat( pszVal, L"1-" );
pProperties = pProperties->pNextSibling; }
if ( AREA_CODE == pProperties->ulId ) { SPDBG_ASSERT( pProperties->pNextSibling );
wcscat( pszVal, L"(" ); SPDBG_ASSERT( pProperties->pFirstChild ); if ( DIGIT == pProperties->pFirstChild->ulId ) { // Area code spelled out digit by digit
if ( 4 != MakeDigitString( pProperties->pFirstChild, pszVal + wcslen( pszVal ), cSize - wcslen( pszVal ) ) ) { return E_INVALIDARG; } } else { // 800 or 900
SPDBG_ASSERT( AREA_CODE == pProperties->pFirstChild->ulId ); wcscat( pszVal, pProperties->pFirstChild->pszValue ); }
wcscat( pszVal, L")-" );
pProperties = pProperties->pNextSibling; }
// Exchange
SPDBG_ASSERT( PHONENUM_EXCHANGE == pProperties->ulId ); SPDBG_ASSERT( pProperties->pFirstChild ); if ( 4 != MakeDigitString( pProperties->pFirstChild, pszVal + wcslen( pszVal ), cSize - wcslen( pszVal ) ) ) { return E_INVALIDARG; } wcscat( pszVal, L"-"); SPDBG_ASSERT( pProperties->pNextSibling ); pProperties = pProperties->pNextSibling;
SPDBG_ASSERT( FOURDIGITS == pProperties->ulId ); SPDBG_ASSERT( pProperties->pFirstChild ); if ( 5 != MakeDigitString( pProperties->pFirstChild, pszVal + wcslen( pszVal ), cSize - wcslen( pszVal ) ) ) { return E_INVALIDARG; } pProperties = pProperties->pNextSibling;
if ( pProperties ) { // extension
SPDBG_ASSERT( EXTENSION == pProperties->ulId ); SPDBG_ASSERT( pProperties->pFirstChild ); wcscat( pszVal, L"x" ); if ( 0 == MakeDigitString( pProperties->pFirstChild, pszVal + wcslen( pszVal ), cSize - wcslen( pszVal ) ) ) { return E_INVALIDARG; } pProperties = pProperties->pNextSibling; }
// Make sure there's nothing else here!
SPDBG_ASSERT( !pProperties );
return S_OK; } /* CTestITN::InterpretPhoneNumber */
/***********************************************************************
* CTestITN::InterpretDegrees * *----------------------------* * Description: * Interprets degrees as a temperature, angle-measurement, * or direction, as appropriate. * Return: * S_OK * E_POINTER * E_INVALIDARG *************************************************************************/ HRESULT CTestITN::InterpretDegrees( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize ) { if ( !pProperties || !pdblVal || !pszVal ) { return E_POINTER; }
*pszVal = 0;
const SPPHRASEPROPERTY *pPropertiesDegree = NULL; const SPPHRASEPROPERTY *pPropertiesMinute = NULL; const SPPHRASEPROPERTY *pPropertiesSecond = NULL; const SPPHRASEPROPERTY *pPropertiesDirection = NULL; const SPPHRASEPROPERTY *pPropertiesUnit = NULL; const SPPHRASEPROPERTY *pPropertiesPtr; // Get the number
for(pPropertiesPtr=pProperties; pPropertiesPtr; pPropertiesPtr=pPropertiesPtr->pNextSibling) { if (TEMP_UNITS == pPropertiesPtr->ulId ) pPropertiesUnit = pPropertiesPtr; else if ( (GRID_INTEGER_NONNEG == pPropertiesPtr->ulId ) || (NUMBER == pPropertiesPtr->ulId )) pPropertiesDegree = pPropertiesPtr; else if (MINUTE == pPropertiesPtr->ulId ) pPropertiesMinute = pPropertiesPtr; else if (SECOND == pPropertiesPtr->ulId ) pPropertiesSecond = pPropertiesPtr; else if (DIRECTION == pPropertiesPtr->ulId ) pPropertiesDirection = pPropertiesPtr; }
SPDBG_ASSERT( pPropertiesDegree ); *pdblVal = pPropertiesDegree->vValue.dblVal; wcscat( pszVal, pPropertiesDegree->pszValue ); wcscat( pszVal, L"�" );
if ( pPropertiesUnit ) { wcscat( pszVal, pPropertiesUnit->pszValue ); } if ( pPropertiesMinute || pPropertiesSecond) { SPDBG_ASSERT( *pdblVal >= 0 ); if ( pPropertiesMinute ) { DOUBLE dblMin = pPropertiesMinute->vValue.dblVal; *pdblVal += dblMin / (DOUBLE) 60; wcscat( pszVal, pPropertiesMinute->pszValue ); wcscat( pszVal, L"'" ); }
if ( pPropertiesSecond ) { DOUBLE dblSec = pPropertiesSecond->vValue.dblVal; *pdblVal += dblSec / (DOUBLE) 3600; wcscat( pszVal, pPropertiesSecond->pszValue ); wcscat( pszVal, L"''" ); } }
if ( pPropertiesDirection ) { wcscat( pszVal, pPropertiesDirection->pszValue ); }
return S_OK; } /* CTestITN::InterpretDegrees */
/***********************************************************************
* CTestITN::InterpretMeasurement * *--------------------------------* * Description: * Interprets measurements, which is a number followed * by a units name * Return: * S_OK * E_POINTER * E_INVALIDARG *************************************************************************/ HRESULT CTestITN::InterpretMeasurement( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize ) { if ( !pProperties || !pdblVal || !pszVal ) { return E_POINTER; }
const SPPHRASEPROPERTY *pPropNumber = NULL; const SPPHRASEPROPERTY *pPropUnits = NULL; const SPPHRASEPROPERTY *pProp; for(pProp= pProperties; pProp; pProp = pProp->pNextSibling) { if (NUMBER == pProp->ulId ) pPropNumber = pProp; else if ( UNITS == pProp->ulId ) pPropUnits = pProp; }
if (!pPropNumber || !pPropUnits) { SPDBG_ASSERT( FALSE ); return E_INVALIDARG; }
if ( cSize < (wcslen(pPropNumber->pszValue) + wcslen(pPropUnits->pszValue) + 1) ) { // Not enough space
return E_INVALIDARG; }
wcscpy( pszVal, pPropNumber->pszValue ); wcscat( pszVal, pPropUnits->pszValue );
*pdblVal = pPropNumber->vValue.dblVal;
return S_OK; } /* CTestITN::InterpretMeasurement */
/***********************************************************************
* CTestITN::InterpretCurrency * *-----------------------------* * Description: * Interprets currency. * Return: * S_OK * E_POINTER if !pdblVal or !pszVal * E_INVALIDARG if the number of cents is not between 0 and 99 * inclusive *************************************************************************/ HRESULT CTestITN::InterpretCurrency( const SPPHRASEPROPERTY *pProperties, DOUBLE *pdblVal, WCHAR *pszVal, UINT cSize) { if ( !pdblVal || !pszVal || !pProperties ) { return E_POINTER; }
const SPPHRASEPROPERTY *pPropDollars = NULL; const SPPHRASEPROPERTY *pPropCents = NULL; const SPPHRASEPROPERTY *pPropType = NULL; const SPPHRASEPROPERTY *pPropSmallType = NULL; const SPPHRASEPROPERTY *pPropNegative = NULL; const SPPHRASEPROPERTY *pProp; for(pProp= pProperties; pProp; pProp = pProp->pNextSibling) { if (NEGATIVE == pProp->ulId ) pPropNegative = pProp; else if ( DOLLARS == pProp->ulId ) pPropDollars = pProp; else if ( CENTS == pProp->ulId ) pPropCents = pProp; else if ( CURRENCY_TYPE == pProp->ulId ) pPropType = pProp; else if ( CURRENCY_SMALL_TYPE == pProp->ulId ) pPropSmallType = pProp; }
*pszVal = 0; *pdblVal = 0;
bool fNonNegative = true; if ( pPropNegative ) { fNonNegative = false; }
bool fUseDefaultCurrencySymbol = true;
if ( pPropDollars ) { // If "dollars" was said, override the default currency symbol
if ( pPropType ) { fUseDefaultCurrencySymbol = false; } }
if ( pPropDollars ) { // Dollars and possibly cents will be here, so we want to
// use regional format
HRESULT hr = GetCurrencyFormatDefaults(); if ( FAILED( hr ) ) { return hr; }
*pdblVal = pPropDollars->vValue.dblVal;
// Handle the case of "$5 million". If this should happen, there
// will be some alphabetic string in the string value for the number,
// and there will be no cents
if ( !pPropCents ) { WCHAR *pwc = wcsstr( pPropDollars->pszValue, MILLION_STR ); if ( !pwc ) { pwc = wcsstr( pPropDollars->pszValue, BILLION_STR ); }
if ( pwc ) { // Either "million" or "billion" was in there
// Just a dollar sign followed by the number string value
if ( !fNonNegative ) { wcscpy( pszVal, m_pwszNeg ); *pdblVal = -*pdblVal; } wcscat( pszVal, pPropType->pszValue ); wcscat( pszVal, pPropDollars->pszValue ); return S_OK; } }
// Use the associated currency symbol
if ( !fUseDefaultCurrencySymbol ) { wcscpy( m_pwszCurrencySym, pPropType->pszValue ); m_cyfmtDefault.lpCurrencySymbol = m_pwszCurrencySym; } // else... use the currency symbol obtained in GetCurrencyFormatDefaults()
if ( pPropCents ) { SPDBG_ASSERT( (pPropCents->vValue.dblVal >= 0) && (pPropCents->vValue.dblVal < 100) ); DOUBLE dblCentsVal = pPropCents->vValue.dblVal / (DOUBLE) 100; if ( *pdblVal >= 0 ) { *pdblVal += dblCentsVal; } else { *pdblVal -= dblCentsVal; } } else { // count up number of decimal places.
// Need to use the original formatted number
// in case someone explicitly gave some zeroes
// as significant digits
const WCHAR *pwszNum = pPropDollars->pszValue; WCHAR pwszNumDecimalSep[ MAX_LOCALE_DATA ]; *pwszNumDecimalSep = 0; int iRet = m_Unicode.GetLocaleInfo( ::GetUserDefaultLCID(), LOCALE_SDECIMAL, pwszNumDecimalSep, MAX_LOCALE_DATA ); WCHAR *pwc = wcsstr( pwszNum, pwszNumDecimalSep );
UINT cDigits = 0; if ( pwc && iRet ) { for ( pwc = pwc + 1; *pwc && iswdigit( *pwc ); pwc++ ) { cDigits++; } } m_cyfmtDefault.NumDigits = __max( m_cyfmtDefault.NumDigits, cDigits ); }
// Handle the negative sign in the value
if ( !fNonNegative ) { *pdblVal = -*pdblVal; } // Write the unformatted number to a string
WCHAR *pwszUnformatted = new WCHAR[ cSize ]; if ( !pwszUnformatted ) { return E_OUTOFMEMORY; } swprintf( pwszUnformatted, L"%f", *pdblVal );
int iRet = m_Unicode.GetCurrencyFormat( LOCALE_USER_DEFAULT, 0, pwszUnformatted, &m_cyfmtDefault, pszVal, cSize ); delete[] pwszUnformatted;
if ( !iRet ) { return E_FAIL; } } else { // Just cents: better have said "cents"
SPDBG_ASSERT( pPropSmallType );
*pdblVal = pPropCents->vValue.dblVal / (DOUBLE) 100;
// Cents are always displayed as 5c, regardless of locale settings.
// Copy over the formatted number
wcscpy( pszVal, pPropCents->pszValue );
// Add on the cents symbol
wcscat( pszVal, pPropSmallType->pszValue ); }
return S_OK; } /* CTestITN::InterpretCurrency */
/***********************************************************************
* CTestITN::AddPropertyAndReplacement * *-------------------------------------* * Description: * Takes all of the info that we want to pass into the * engine site, forms the SPPHRASEPROPERTY and * SPPHRASERREPLACEMENT, and adds them to the engine site * Return: * Return values of ISpCFGInterpreterSite::AddProperty() * and ISpCFGInterpreterSite::AddTextReplacement() *************************************************************************/ HRESULT CTestITN::AddPropertyAndReplacement( const WCHAR *szBuff, const DOUBLE dblValue, const ULONG ulMinPos, const ULONG ulMaxPos, const ULONG ulFirstElement, const ULONG ulCountOfElements, const BYTE bDisplayAttribs ) { // Add the property
SPPHRASEPROPERTY prop; memset(&prop,0,sizeof(prop)); prop.pszValue = szBuff; prop.vValue.vt = VT_R8; prop.vValue.dblVal = dblValue; prop.ulFirstElement = ulMinPos; prop.ulCountOfElements = ulMaxPos - ulMinPos; HRESULT hr = m_pSite->AddProperty(&prop);
if (SUCCEEDED(hr)) { SPPHRASEREPLACEMENT repl; memset(&repl,0, sizeof(repl)); repl.bDisplayAttributes = bDisplayAttribs; repl.pszReplacementText = szBuff; repl.ulFirstElement = ulFirstElement; repl.ulCountOfElements = ulCountOfElements; hr = m_pSite->AddTextReplacement(&repl); }
return hr; } /* CTestITN::AddPropertyAndReplacement */
// Helper functions
/***********************************************************************
* CTestITN::MakeDisplayNumber * *-----------------------------* * Description: * Converts a DOUBLE into a displayable * number in the range -999,999,999,999 to +999,999,999,999. * cSize is the number of chars for which pwszNum has space * allocated. * If DF_UNFORMATTED is set, all other flags are ignored, * and the number is passed back as an optional negative * followed by a string of digits * If DF_ORDINAL is set in dwDisplayFlags, displays an * ordinal number (i.e. tacks on "th" or the appropriate suffix. * If DF_WHOLENUMBER is set, lops off the decimal separator * and everything after it. If DF_WHOLENUMBER is not set, * then uses the uiDecimalPlaces parameter to determine * how many decimal places to display * If DF_FIXEDWIDTH is set, will display at least uiFixedWidth * digits; otherwise uiFixedWidth is ignored. * If DF_NOTHOUSANDSGROUP is set, will not do thousands * grouping (commas) *************************************************************************/ HRESULT CTestITN::MakeDisplayNumber( DOUBLE dblNum, DWORD dwDisplayFlags, UINT uiFixedWidth, UINT uiDecimalPlaces, WCHAR *pwszNum, UINT cSize ) { SPDBG_ASSERT( pwszNum ); SPDBG_ASSERT( !SPIsBadWritePtr( pwszNum, cSize ) ); *pwszNum = 0;
// Get the default number formatting.
// Note that this gets called every time, since otherwise there
// is no way to pick up changes that the user has made since
// this process has started.
HRESULT hr = GetNumberFormatDefaults(); if ( FAILED( hr ) ) { return hr; } // check for straight millions and straight billions
// NB: This is a workaround for the fact that we can't resolve the ambiguity
// and get "two million" to go through GRID_INTEGER_MILLBILL
if (( dwDisplayFlags & DF_WHOLENUMBER ) && ( dwDisplayFlags & DF_MILLIONBILLION ) && (dblNum > 0)) { HRESULT hr; if ( 0 == (( ((LONGLONG) dblNum) % BILLION )) ) { // e.g. for "five billion" get the "5" and then
// tack on " billion"
hr = MakeDisplayNumber( ( dblNum / ((DOUBLE) BILLION) ), dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pwszNum, cSize ); if ( SUCCEEDED( hr ) ) { wcscat( pwszNum, L" " ); wcscat( pwszNum, BILLION_STR ); } return hr; } else if (( ((LONGLONG) dblNum) < BILLION ) && ( 0 == (( ((LONGLONG) dblNum) % MILLION )) )) { hr = MakeDisplayNumber( ( dblNum / ((DOUBLE) MILLION) ), dwDisplayFlags, uiFixedWidth, uiDecimalPlaces, pwszNum, cSize ); if ( SUCCEEDED( hr ) ) { wcscat( pwszNum, L" " ); wcscat( pwszNum, MILLION_STR ); } return hr; } }
// Put in the negative sign if necessary
if ( dblNum < 0 ) { wcscat( pwszNum, L"-" );
// From now on we want to deal with the magnitude of the number
dblNum *= -1; } SPDBG_ASSERT( dblNum < 1e12 );
WCHAR *pwszTemp = new WCHAR[ cSize ]; if ( !pwszTemp ) { return E_OUTOFMEMORY; } *pwszTemp = 0;
LONGLONG llIntPart = (LONGLONG) dblNum; UINT64 uiDigitsLeftOfDecimal; if ( dwDisplayFlags & DF_WHOLENUMBER ) { swprintf( pwszTemp, L"%I64d", llIntPart ); uiDigitsLeftOfDecimal = wcslen( pwszTemp ); } else { swprintf( pwszTemp, L"%.*f", uiDecimalPlaces, dblNum ); WCHAR *pwc = wcschr( pwszTemp, L'.' ); uiDigitsLeftOfDecimal = pwc - pwszTemp; } // The following handles the case where the user said something
// like "zero zero zero three" and wants to see "0,003"
BOOL fChangedFirstDigit = false; const WCHAR wcFakeFirstDigit = L'1'; if ( !(dwDisplayFlags & DF_UNFORMATTED) && (dwDisplayFlags & DF_FIXEDWIDTH) && (uiDigitsLeftOfDecimal < uiFixedWidth) ) { // The following handles the case where the user wants leading
// zeroes displayed
// Need to pad the front with zeroes
for ( UINT ui = 0; ui < (uiFixedWidth - uiDigitsLeftOfDecimal); ui++ ) { wcscat( pwszNum, L"0" ); } // HACK
// In order to force something like "zero zero zero three"
// into the form "0,003", we need to make GetNumberFormat()
// think that the first digit is 1.
WCHAR *pwc = wcschr( pwszNum, L'0' ); SPDBG_ASSERT( pwc ); *pwc = wcFakeFirstDigit; fChangedFirstDigit = true; }
// Copy over the unformatted number after the possible negative sign
wcscat( pwszNum, pwszTemp ); delete[] pwszTemp;
// If we do not want to format the number, then bail here
if ( dwDisplayFlags & DF_UNFORMATTED ) { return S_OK; }
// Make a copy so that we can change some fields according to the
// flags param
NUMBERFMTW nmfmt = m_nmfmtDefault;
// How many decimal places to display?
if ( dwDisplayFlags & DF_WHOLENUMBER ) { nmfmt.NumDigits = 0; } else { // Use the uiDecimalPlaces value to determine how
// many to display
nmfmt.NumDigits = uiDecimalPlaces; } // Leading zeroes?
nmfmt.LeadingZero = (dwDisplayFlags & DF_LEADINGZERO) ? 1 : 0;
// Thousands grouping?
if ( dwDisplayFlags & DF_NOTHOUSANDSGROUP ) { nmfmt.Grouping = 0; }
// Format the number string
WCHAR *pwszFormattedNum = new WCHAR[ cSize ]; if ( !pwszFormattedNum ) { return E_OUTOFMEMORY; } *pwszFormattedNum = 0;
int iRet; do { iRet = m_Unicode.GetNumberFormat( LOCALE_USER_DEFAULT, 0, pwszNum, &nmfmt, pwszFormattedNum, cSize ); if ( !iRet && nmfmt.NumDigits ) { // Try displaying fewer digits
nmfmt.NumDigits--; } } while ( !iRet && nmfmt.NumDigits ); SPDBG_ASSERT( iRet );
// Copy the formatted number into pwszNum
wcscpy( pwszNum, pwszFormattedNum ); delete[] pwszFormattedNum;
// This undoes the hack of changing the first digit
if ( fChangedFirstDigit ) { // We need to find the first digit and change it back to zero
WCHAR *pwc = wcschr( pwszNum, wcFakeFirstDigit ); SPDBG_ASSERT( pwc ); *pwc = L'0'; }
if ( dwDisplayFlags & DF_ORDINAL ) { SPDBG_ASSERT( dwDisplayFlags & DF_WHOLENUMBER ); // sanity
// This is an ordinal number, tack on the appropriate suffix
// The "st", "nd", "rd" endings only happen when you
// don't have something like "twelfth"
if ( ((llIntPart % 100) < 10) || ((llIntPart % 100) > 20) ) { switch ( llIntPart % 10 ) { case 1: wcscat( pwszNum, L"st" ); break; case 2: wcscat( pwszNum, L"nd" ); break; case 3: wcscat( pwszNum, L"rd" ); break; default: wcscat( pwszNum, L"th" ); break; } } else { wcscat( pwszNum, L"th" ); } }
return S_OK;
} /* CTestITN::MakeDisplayNumber */
/***********************************************************************
* CTestITN::MakeDigitString * *---------------------------* * Description: * Called when we want to convert a string of DIGITs into * a string but don't care about its value * Return: * Number of digits written to the string, including nul * character *************************************************************************/ int CTestITN::MakeDigitString( const SPPHRASEPROPERTY *pProperties, WCHAR *pwszDigitString, UINT cSize ) { if ( !pProperties || !pwszDigitString ) { return 0; }
UINT cCount = 0; for ( ; pProperties; pProperties = pProperties->pNextSibling ) { if ( DIGIT != pProperties->ulId ) { return 0; }
if ( cSize-- <= 0 ) { // Not enough space
return 0; } pwszDigitString[ cCount++ ] = pProperties->pszValue[0]; }
pwszDigitString[ cCount++ ] = 0;
return cCount; } /* CTestITN::MakeDigitString */
/***********************************************************************
* CTestITN::GetNumberFormatDefaults * *-----------------------------------* * Description: * This finds all of the defaults for formatting numbers for * this user. *************************************************************************/ HRESULT CTestITN::GetNumberFormatDefaults() { LCID lcid = ::GetUserDefaultLCID(); WCHAR pwszLocaleData[ MAX_LOCALE_DATA ]; int iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_IDIGITS, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_nmfmtDefault.NumDigits = _wtoi( pwszLocaleData ); iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_ILZERO, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } // It's always either 0 or 1
m_nmfmtDefault.LeadingZero = _wtoi( pwszLocaleData );
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SGROUPING, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } // It will look like single_digit;0, or else it will look like
// 3;2;0
UINT uiGrouping = *pwszLocaleData - L'0'; if ( (3 == uiGrouping) && (L';' == pwszLocaleData[1]) && (L'2' == pwszLocaleData[2]) ) { uiGrouping = 32; } m_nmfmtDefault.Grouping = uiGrouping;
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SDECIMAL, m_pwszDecimalSep, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_nmfmtDefault.lpDecimalSep = m_pwszDecimalSep;
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_STHOUSAND, m_pwszThousandSep, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_nmfmtDefault.lpThousandSep = m_pwszThousandSep;
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_INEGNUMBER, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_nmfmtDefault.NegativeOrder = _wtoi( pwszLocaleData );
// Get the negative sign
delete[] m_pwszNeg; iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SNEGATIVESIGN, NULL, 0); if ( !iRet ) { return E_FAIL; } m_pwszNeg = new WCHAR[ iRet ]; if ( !m_pwszNeg ) { return E_OUTOFMEMORY; } iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SNEGATIVESIGN, m_pwszNeg, iRet );
return iRet ? S_OK : E_FAIL; } /* CTestITN::GetNumberFormatDefaults */
/***********************************************************************
* CTestITN::GetCurrencyFormatDefaults * *-----------------------------------* * Description: * This finds all of the defaults for formatting numbers for * this user. *************************************************************************/ HRESULT CTestITN::GetCurrencyFormatDefaults() { LCID lcid = ::GetUserDefaultLCID(); WCHAR pwszLocaleData[ MAX_LOCALE_DATA ]; int iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_ICURRDIGITS, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_cyfmtDefault.NumDigits = _wtoi( pwszLocaleData );
// NB: A value of zero is bogus for LOCALE_ILZERO, since
// currency should always display leading zero
m_cyfmtDefault.LeadingZero = 1;
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SMONGROUPING, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } // It will look like single_digit;0, or else it will look like
// 3;2;0
UINT uiGrouping = *pwszLocaleData - L'0'; if ( (3 == uiGrouping) && (L';' == pwszLocaleData[1]) && (L'2' == pwszLocaleData[2]) ) { uiGrouping = 32; } m_cyfmtDefault.Grouping = uiGrouping;
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SMONDECIMALSEP, m_pwszDecimalSep, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_cyfmtDefault.lpDecimalSep = m_pwszDecimalSep;
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SMONTHOUSANDSEP, m_pwszThousandSep, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_cyfmtDefault.lpThousandSep = m_pwszThousandSep;
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_INEGCURR, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_cyfmtDefault.NegativeOrder = _wtoi( pwszLocaleData );
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_ICURRENCY, pwszLocaleData, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_cyfmtDefault.PositiveOrder = _wtoi( pwszLocaleData );
iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SCURRENCY, m_pwszCurrencySym, MAX_LOCALE_DATA ); if ( !iRet ) { return E_FAIL; } m_cyfmtDefault.lpCurrencySymbol = m_pwszCurrencySym;
return S_OK; } /* CTestITN::GetCurrencyFormatDefaults */
/***********************************************************************
* CTestITN::ComputeNum999 * *-------------------------* * Description: * Converts a set of SPPHRASEPROPERTYs into a number in * [-999, 999]. * The way these properties is structured is that the top-level * properties contain the place of the number (100s, 10s, 1s) * by having the value 100, 10, or 1. * The child has the appropriate number value. * Return: * Value of the number *************************************************************************/ ULONG CTestITN::ComputeNum999(const SPPHRASEPROPERTY *pProperties )//, ULONG *pVal)
{ ULONG ulVal = 0;
for (const SPPHRASEPROPERTY * pProp = pProperties; pProp; pProp = pProp->pNextSibling) { if ( ZERO != pProp->ulId ) { SPDBG_ASSERT( pProp->pFirstChild ); SPDBG_ASSERT( VT_UI4 == pProp->vValue.vt ); SPDBG_ASSERT( VT_UI4 == pProp->pFirstChild->vValue.vt );
ulVal += pProp->pFirstChild->vValue.ulVal * pProp->vValue.ulVal; } } return ulVal; } /* CTestITN::ComputeNum999 */
/***********************************************************************
* CTestITN::HandleDigitsAfterDecimal * *------------------------------------* * Description: * If pwszRightOfDecimal is NULL, then cuts off all of the numbers * following the decimal separator. * Otherwise, copies pwszRightOfDecimal right after the decimal * separator in pwszFormattedNum. * Preserves the stuff after the digits in the pwszFormattedNum * (e.g. if pwszFormattedNum starts out "(3.00)" and * pwszRightOfDecimal is NULL, then pwszFormattedNum will end * up as "(3)" *************************************************************************/ void CTestITN::HandleDigitsAfterDecimal( WCHAR *pwszFormattedNum, UINT cSizeOfFormattedNum, const WCHAR *pwszRightOfDecimal ) { SPDBG_ASSERT( pwszFormattedNum ); // First need to find what the decimal string is
LCID lcid = ::GetUserDefaultLCID(); WCHAR pwszDecimalString[ MAX_LOCALE_DATA ]; // Guaranteed to be no longer than 4 long
int iRet = m_Unicode.GetLocaleInfo( lcid, LOCALE_SDECIMAL, pwszDecimalString, MAX_LOCALE_DATA ); SPDBG_ASSERT( iRet );
WCHAR *pwcDecimal = wcsstr( pwszFormattedNum, pwszDecimalString ); SPDBG_ASSERT( pwcDecimal );
// pwcAfterDecimal points to the first character after the decimal separator
WCHAR *pwcAfterDecimal = pwcDecimal + wcslen( pwszDecimalString );
// Remember what originally followed the digits
WCHAR *pwszTemp = new WCHAR[ cSizeOfFormattedNum ]; WCHAR *pwcAfterDigits; // points to the first character after the end of the digits
for ( pwcAfterDigits = pwcAfterDecimal; *pwcAfterDigits && iswdigit( *pwcAfterDigits ); pwcAfterDigits++ ) ; wcscpy( pwszTemp, pwcAfterDigits ); // OK if *pwcAfterDigits == 0
if ( pwszRightOfDecimal ) { // This means that the caller wants the digits in pwszRightOfDecimal
// copied after the decimal separator
// Copy the decimal string after the decimal separater
wcscpy( pwcAfterDecimal, pwszRightOfDecimal );
} else { // This means that the caller wanted the decimal separator
// and all text following it stripped off
*pwcDecimal = 0; }
// Add on the extra after-digit characters
wcscat( pwszFormattedNum, pwszTemp );
delete[] pwszTemp;
} /* CTestITN::HandleDigitsAfterDecimal */
/***********************************************************************
* CTestITN::GetMinAndMaxPos * *---------------------------* * Description: * Gets the minimum and maximum elements spanned by the * set of properties *************************************************************************/ void CTestITN::GetMinAndMaxPos( const SPPHRASEPROPERTY *pProperties, ULONG *pulMinPos, ULONG *pulMaxPos ) { if ( !pulMinPos || !pulMaxPos ) { return; } ULONG ulMin = 9999999; ULONG ulMax = 0;
for ( const SPPHRASEPROPERTY *pProp = pProperties; pProp; pProp = pProp->pNextSibling ) { ulMin = __min( ulMin, pProp->ulFirstElement ); ulMax = __max( ulMax, pProp->ulFirstElement + pProp->ulCountOfElements ); } *pulMinPos = ulMin; *pulMaxPos = ulMax; } /* CTestITN::GetMinAndMaxPos */
/***********************************************************************
* CTestITN::GetMonthName * *------------------------* * Description: * Gets the name of the month, abbreviated if desired * Return: * Number of characters written to pszMonth, 0 if failed *************************************************************************/ int CTestITN::GetMonthName( int iMonth, WCHAR *pwszMonth, int cSize, bool fAbbrev ) { LCTYPE lctype; switch ( iMonth ) { case 1: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME1 : LOCALE_SMONTHNAME1; break; case 2: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME2 : LOCALE_SMONTHNAME2; break; case 3: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME3 : LOCALE_SMONTHNAME3; break; case 4: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME4 : LOCALE_SMONTHNAME4; break; case 5: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME5 : LOCALE_SMONTHNAME5; break; case 6: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME6 : LOCALE_SMONTHNAME6; break; case 7: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME7 : LOCALE_SMONTHNAME7; break; case 8: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME8 : LOCALE_SMONTHNAME8; break; case 9: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME9 : LOCALE_SMONTHNAME9; break; case 10: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME10 : LOCALE_SMONTHNAME10; break; case 11: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME11 : LOCALE_SMONTHNAME11; break; case 12: lctype = fAbbrev ? LOCALE_SABBREVMONTHNAME12 : LOCALE_SMONTHNAME12; break; default: return 0; }
int iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, lctype, pwszMonth, cSize );
return iRet; } /* CTestITN::GetMonthName */
/***********************************************************************
* CTestITN::GetDayOfWeekName * *----------------------------* * Description: * Gets the name of the day of week, abbreviated if desired * Return: * Number of characters written to pszDayOfWeek, 0 if failed *************************************************************************/ int CTestITN::GetDayOfWeekName( int iDayOfWeek, WCHAR *pwszDayOfWeek, int cSize, bool fAbbrev ) { LCTYPE lctype; switch ( iDayOfWeek ) { case 1: lctype = fAbbrev ? LOCALE_SABBREVDAYNAME1 : LOCALE_SDAYNAME1; break; case 2: lctype = fAbbrev ? LOCALE_SABBREVDAYNAME2 : LOCALE_SDAYNAME2; break; case 3: lctype = fAbbrev ? LOCALE_SABBREVDAYNAME3 : LOCALE_SDAYNAME3; break; case 4: lctype = fAbbrev ? LOCALE_SABBREVDAYNAME4 : LOCALE_SDAYNAME4; break; case 5: lctype = fAbbrev ? LOCALE_SABBREVDAYNAME5 : LOCALE_SDAYNAME5; break; case 6: lctype = fAbbrev ? LOCALE_SABBREVDAYNAME6 : LOCALE_SDAYNAME6; break; case 7: lctype = fAbbrev ? LOCALE_SABBREVDAYNAME7 : LOCALE_SDAYNAME7; break; default: return 0; }
int iRet = m_Unicode.GetLocaleInfo( LOCALE_USER_DEFAULT, lctype, pwszDayOfWeek, cSize );
return iRet; } /* CTestITN::GetMonthName */
/***********************************************************************
* CTestITN::FormatDate * *----------------------* * Description: * Uses the format string to format a SYSTEMTIME date. * We are using this instead of GetDateFormat() since * we also want to format bogus dates and dates with * years like 1492 that are not accepted by GetDateFormat() * Return: * Number of characters written to pszDate (including * null terminating character), 0 if failed *************************************************************************/ int CTestITN::FormatDate( const SYSTEMTIME &stDate, WCHAR *pwszFormat, WCHAR *pwszDate, int cSize ) { if ( !pwszFormat || !pwszDate ) { SPDBG_ASSERT( FALSE ); return 0; }
WCHAR * const pwszDateStart = pwszDate;
WCHAR *pwc = pwszFormat;
// Copy the format string to the date string character by
// character, replacing the string like "dddd" as appropriate
while ( *pwc ) { switch( *pwc ) { case L'd': { // Count the number of d's
int cNumDs = 0; int iRet; do { pwc++; cNumDs++; } while ( L'd' == *pwc ); switch ( cNumDs ) { case 1: // Day with no leading zeroes
swprintf( pwszDate, L"%d", stDate.wDay ); iRet = wcslen( pwszDate ); break; case 2: // Day with one fixed width of 2
swprintf( pwszDate, L"%02d", stDate.wDay ); iRet = wcslen( pwszDate ); break; case 3: // Abbreviated day of week
iRet = GetDayOfWeekName( stDate.wDayOfWeek, pwszDate, cSize, true ) - 1; break; default: // More than 4? Treat it as 4
// Day of week
iRet = GetDayOfWeekName( stDate.wDayOfWeek, pwszDate, cSize, false ) - 1; break; }
if ( iRet <= 0 ) { return 0; } else { pwszDate += iRet; } break; }
case L'M': { // Count the number of M's
int cNumMs = 0; int iRet; do { pwc++; cNumMs++; } while ( L'M' == *pwc ); switch ( cNumMs ) { case 1: // Day with no leading zeroes
swprintf( pwszDate, L"%d", stDate.wMonth ); iRet = wcslen( pwszDate ); break; case 2: // Day with one fixed width of 2
swprintf( pwszDate, L"%02d", stDate.wMonth ); iRet = wcslen( pwszDate ); break; case 3: // Abbreviated month name
iRet = GetMonthName( stDate.wMonth, pwszDate, cSize, true ) - 1; break; default: // More than 4? Treat it as 4
// Month
iRet = GetMonthName( stDate.wMonth, pwszDate, cSize, false ) - 1; break; }
if ( iRet < 0 ) { return 0; } else { pwszDate += iRet; } break; } case L'y': { // Count the number of y's
int cNumYs = 0; do { pwc++; cNumYs++; } while ( L'y' == *pwc );
// More than 4 y's: consider it as 4 y's
if ( cNumYs > 4 ) { cNumYs = 4; }
if (( cNumYs >= 3 ) && ( stDate.wYear < 100 )) { // "Ninety nine": Should display as "'99"
cNumYs = 2;
*pwszDate++ = L'\''; }
switch ( cNumYs ) { case 1: case 2: // Last two digits of year, width of 2
swprintf( pwszDate, (1 == cNumYs ) ? L"%d" : L"%02d", stDate.wYear % 100 ); pwszDate += 2; break; case 3: case 4: // All four digits of year, width of 4
// Last two digits of year, width of 2
swprintf( pwszDate, L"%04d", stDate.wYear % 10000 ); pwszDate += 4; break; } break; }
default: *pwszDate++ = *pwc++; } } *pwszDate++ = 0;
return (int) (pwszDate - pwszDateStart); } /* CTestITN::FormatDate */
/***********************************************************************
* CTestITN::MakeNumberNegative * *------------------------------* * Description: * Uses the current number format defaults to transform * pszNumber into a negative number * Return: * S_OK * E_OUTOFMEMORY *************************************************************************/ HRESULT CTestITN::MakeNumberNegative( WCHAR *pwszNumber ) { HRESULT hr = GetNumberFormatDefaults(); if ( FAILED( hr ) ) { return hr; }
// Create a temporary buffer with the non-negated number in it
WCHAR *pwszTemp = wcsdup( pwszNumber ); if ( !pwszTemp ) { return E_OUTOFMEMORY; }
switch ( m_nmfmtDefault.NegativeOrder ) { case 0: // (1.1)
wcscpy( pwszNumber, L"(" ); wcscat( pwszNumber, pwszTemp ); wcscat( pwszNumber, L")" ); break;
case 1: case 2: // 1: -1.1 2: - 1.1
wcscpy( pwszNumber, m_pwszNeg ); if ( 2 == m_nmfmtDefault.NegativeOrder ) { wcscat( pwszNumber, L" " ); } wcscat( pwszNumber, pwszTemp ); break;
case 3: case 4: // 3: 1.1- 4: 1.1 -
wcscpy( pwszNumber, pwszTemp ); if ( 4 == m_nmfmtDefault.NegativeOrder ) { wcscat( pwszNumber, L" " ); } wcscat( pwszNumber, m_pwszNeg ); break;
default: SPDBG_ASSERT( false ); break; }
free( pwszTemp );
return S_OK; } /* CTestITN::MakeNumberNegative */
|