windows-nt/Source/XPSP1/NT/enduser/speech/sapi/sapi/phoneconv.cpp
2020-09-26 16:20:57 +08:00

724 lines
20 KiB
C++

/*******************************************************************************
* PhoneConv.cpp
* Phone convertor object. Converts between internal phone and Id phone set.
*
* Owner: YUNUSM/YUNCJ Date: 06/18/99
* Copyright (C) 1999 Microsoft Corporation. All Rights Reserved.
*******************************************************************************/
//--- Includes -----------------------------------------------------------------
#include "stdafx.h"
#include "resource.h"
#include "PhoneConv.h"
#include "a_helpers.h"
//--- Constants ----------------------------------------------------------------
/*******************************************************************************
* CSpPhoneConverter::CSpPhoneConverter *
*--------------------------------------*
* Description:
* Constructor
* Result:
* n/a
***************************************************************** YUNUSM ******/
CSpPhoneConverter::CSpPhoneConverter()
{
SPDBG_FUNC("CPhoneConv::CSpPhoneConverter");
m_pPhoneId = NULL;
m_pIdIdx = NULL;
m_dwPhones = 0;
m_cpObjectToken = NULL;
m_fNoDelimiter = FALSE;
#ifdef SAPI_AUTOMATION
m_LangId = 0;
#endif //SAPI_AUTOMATION
}
/*******************************************************************************
* CSpPhoneConverter::~CSpPhoneConverter *
*---------------------------------------*
* Description:
* Destructor
* Result:
* n/a
***************************************************************** YUNUSM ******/
CSpPhoneConverter::~CSpPhoneConverter()
{
SPDBG_FUNC("CSpPhoneConverter::~CSpPhoneConverter");
delete [] m_pPhoneId;
free(m_pIdIdx);
}
STDMETHODIMP CSpPhoneConverter::SetObjectToken(ISpObjectToken * pToken)
{
SPDBG_FUNC("CSpPhoneConverter::SetObjectToken");
HRESULT hr = S_OK;
hr = SpGenericSetObjectToken(pToken, m_cpObjectToken);
// Try to read the phone map from the token
CSpDynamicString dstrPhoneMap;
if (SUCCEEDED(hr))
{
hr = pToken->GetStringValue(L"PhoneMap", &dstrPhoneMap);
}
if(SUCCEEDED(hr))
{
BOOL fNoDelimiter;
hr = pToken->MatchesAttributes(L"NoDelimiter", &fNoDelimiter);
if(SUCCEEDED(hr))
{
m_fNoDelimiter = fNoDelimiter;
}
}
BOOL fNumericPhones;
if(SUCCEEDED(hr))
{
hr = pToken->MatchesAttributes(L"NumericPhones", &fNumericPhones);
}
// Set it on ourselves
if (SUCCEEDED(hr))
{
hr = SetPhoneMap(dstrPhoneMap, fNumericPhones);
}
SPDBG_REPORT_ON_FAIL(hr);
return hr;
}
STDMETHODIMP CSpPhoneConverter::GetObjectToken(ISpObjectToken **ppToken)
{
return SpGenericGetObjectToken(ppToken, m_cpObjectToken);
}
/*******************************************************************************
* CSpPhoneConverter::PhoneToId *
*-------------------------------*
*
* Description:
* Convert an internal phone string to Id code string
* The internal phones are space separated and may have a space
* at the end.
*
* Return:
* S_OK
* E_POINTER
* E_INVALIDARG
***************************************************************** YUNUSM ******/
STDMETHODIMP CSpPhoneConverter::PhoneToId(const WCHAR *pszIntPhone, // Internal phone string
SPPHONEID *pId // Returned Id string
)
{
SPDBG_FUNC("CSpPhoneConverter::PhoneToId");
if (!pszIntPhone || SPIsBadStringPtr(pszIntPhone))
{
return E_INVALIDARG;
}
else if (wcslen(pszIntPhone) >= SP_MAX_PRON_LENGTH * (g_dwMaxLenPhone + 1))
{
return E_INVALIDARG;
}
else if (m_pPhoneId == NULL)
{
return SPERR_UNINITIALIZED;
}
HRESULT hr = S_OK;
SPPHONEID pidArray[SP_MAX_PRON_LENGTH];
pidArray[0] = L'\0';
SPPHONEID *pidPos = pidArray;
WCHAR szPhone[g_dwMaxLenPhone + 1];
const WCHAR *p = pszIntPhone, *p1;
while (SUCCEEDED(hr) && p)
{
// skip over leading spaces
while (*p && *p == L' ')
p++;
if (!*p)
{
p = NULL;
break;
}
if(m_fNoDelimiter)
{
p1 = p + 1;
}
else
{
p1 = wcschr(p, L' ');
if(!p1)
{
p1 = p + wcslen(p);
}
}
if(p1 - p > g_dwMaxLenPhone)
{
hr = E_INVALIDARG;
break;
}
wcsncpy(szPhone, p, p1 - p);
szPhone[p1 - p] = L'\0';
// Search for this phone
int i = 0;
int j = m_dwPhones - 1;
while (i <= j)
{
int l = wcsicmp(szPhone, m_pPhoneId[(i+j)/2].szPhone);
if (l > 0)
i = (i+j)/2 + 1;
else if (l < 0)
j = (i+j)/2 - 1;
else
{
// found
if ((pidPos - pidArray) > (SP_MAX_PRON_LENGTH - g_dwMaxLenId -1))
hr = E_FAIL;
else
{
wcscpy(pidPos, m_pPhoneId[(i+j)/2].pidPhone);
pidPos += wcslen(pidPos);
}
break;
}
}
if (i > j)
hr = E_INVALIDARG; // Phone not found
p = p1;
}
if (SUCCEEDED(hr))
hr = SPCopyPhoneString(pidArray, pId);
return hr;
} /* CSpPhoneConverter::PhoneToId */
/*******************************************************************************
* CSpPhoneConverter::IdToPhone *
*-------------------------------*
*
* Description:
* Convert an Id code string to internal phone.
* The output internal phones are space separated.
*
* Return:
* S_OK
* E_POINTER
* E_INVALIDARG
***************************************************************** YUNUSM ******/
STDMETHODIMP CSpPhoneConverter::IdToPhone(const SPPHONEID *pId, // Id string
WCHAR *pszIntPhone // Returned Internal phone string
)
{
SPDBG_FUNC("CSpPhoneConverter::IdToPhone");
if (SPIsBadStringPtr(pId))
{
return E_POINTER;
}
else if (wcslen(pId) >= SP_MAX_PRON_LENGTH)
{
return E_INVALIDARG;
}
else if (m_pPhoneId == NULL)
{
return SPERR_UNINITIALIZED;
}
HRESULT hr = S_OK;
WCHAR szPhone[SP_MAX_PRON_LENGTH * (g_dwMaxLenPhone + 1)];
DWORD nLen = wcslen (pId);
DWORD nOffset = 0;
WCHAR *p = szPhone;
*p = NULL;
while (SUCCEEDED(hr) && nLen)
{
SPPHONEID pidStr[g_dwMaxLenId +1];
DWORD nCompare = (nLen > g_dwMaxLenId) ? g_dwMaxLenId : nLen;
wcsncpy (pidStr, pId + nOffset, nCompare);
pidStr[nCompare] = L'\0';
for (;;)
{
int i = 0;
int j = m_dwPhones - 1;
while (i <= j)
{
int cmp = wcscmp(m_pIdIdx[(i+j)/2]->pidPhone, pidStr);
if(cmp > 0)
{
j = (i+j)/2 - 1;
}
else if(cmp <0)
{
i = (i+j)/2 + 1;
}
else
{
break;
}
}
if (i <= j)
{
// found
// 2 for the seperating space and terminating NULL
if ((DWORD)(p - szPhone) > (SP_MAX_PRON_LENGTH * (g_dwMaxLenPhone + 1) - g_dwMaxLenPhone - 2))
hr = E_FAIL;
else
{
if (!m_fNoDelimiter && p != szPhone)
{
wcscat (p, L" ");
}
// Check if its an Id that this phone-set doesnt care about
if (wcscmp(m_pIdIdx[(i+j)/2]->szPhone, L"##"))
{
wcscat (p, m_pIdIdx[(i+j)/2]->szPhone);
p += wcslen (p);
}
// Here 'p' is always pointing to a NULL so the above strcats work fine
break;
}
}
pidStr[--nCompare] = L'\0';
if (!nCompare)
{
*szPhone = NULL;
hr = E_INVALIDARG;
break;
}
} // for (;;)
nLen -= nCompare;
nOffset += nCompare;
} // while (nLen)
if (SUCCEEDED(hr))
hr = SPCopyPhoneString(szPhone, pszIntPhone);
return hr;
} /* CSpPhoneConverter::IdToPhone */
/*******************************************************************************
* ComparePhone *
*--------------*
* Description:
* Compares two internal phones
* Result:
* 0, 1, -1
***************************************************************** YUNUSM ******/
int __cdecl ComparePhone(const void* p1, const void* p2)
{
SPDBG_FUNC("ComparePhone");
return wcsicmp(((PHONEMAPNODE*)p1)->szPhone, ((PHONEMAPNODE*)p2)->szPhone);
} /* ComparePhone */
/*******************************************************************************
* CompareId *
*-----------*
* Description:
* Compares two Id chars
* Result:
* 0, 1, -1
***************************************************************** YUNUSM ******/
int CompareId(const void *p1, const void *p2)
{
SPDBG_FUNC("CompareId");
return wcscmp((*((PHONEMAPNODE**)p1))->pidPhone, (*((PHONEMAPNODE**)p2))->pidPhone);
} /* CompareId */
/*******************************************************************************
* CSpPhoneConverter::SetPhoneMap *
*--------------------------------*
*
* Description:
* Sets the phone map
*
* Return:
* S_OK
* E_POINTER
* E_INVALIDARG
* E_OUTOFMEMORY
***************************************************************** YUNUSM ******/
HRESULT CSpPhoneConverter::SetPhoneMap(const WCHAR *pwMap, BOOL fNumericPhones)
{
SPDBG_FUNC("CPhoneConv::SetPhoneMap");
HRESULT hr = S_OK;
const WCHAR *p = pwMap;
DWORD k = 0;
// Count the number of phones
while (*p)
{
while (*p && *p == L' ')
p++;
if (!*p)
break;
m_dwPhones++;
while (*p && *p != L' ')
p++;
}
if (!m_dwPhones || m_dwPhones % 2 || m_dwPhones > 32000)
hr = E_INVALIDARG;
// Alloc the data structures
if (SUCCEEDED(hr))
{
m_pPhoneId = new PHONEMAPNODE[m_dwPhones / 2];
if (!m_pPhoneId)
hr = E_OUTOFMEMORY;
else
ZeroMemory(m_pPhoneId, sizeof(PHONEMAPNODE) * (m_dwPhones / 2));
}
// Read the data
if (SUCCEEDED(hr))
{
const WCHAR *pPhone = pwMap, *pEnd;
for (k = 0; SUCCEEDED(hr) && k < m_dwPhones; k++)
{
// Get the next phone
while (*pPhone && *pPhone == L' ')
pPhone++;
pEnd = pPhone;
while (*pEnd && *pEnd != L' ')
pEnd++;
if (!(k % 2))
{
if(fNumericPhones)
{
// wchar phone but stored as a 4 character hex string
if ((pEnd - pPhone) % 4 ||
pEnd - pPhone > g_dwMaxLenPhone * 4)
{
hr = E_INVALIDARG;
continue;
}
WCHAR szId[(g_dwMaxLenPhone + 1) * 4];
wcsncpy(szId, pPhone, pEnd - pPhone);
szId[pEnd - pPhone] = L'\0';
// Convert the space separated hex values to array of WCHARS
ahtoi(szId, m_pPhoneId[k / 2].szPhone);
}
else
{
// wchar phone
if (pEnd - pPhone > g_dwMaxLenPhone)
{
hr = E_INVALIDARG;
continue;
}
wcsncpy(m_pPhoneId[k / 2].szPhone, pPhone, pEnd - pPhone);
(m_pPhoneId[k / 2].szPhone)[pEnd - pPhone] = L'\0';
}
}
else
{
// Id Phone
if ((pEnd - pPhone) % 4 ||
pEnd - pPhone > g_dwMaxLenId * 4)
{
hr = E_INVALIDARG;
continue;
}
WCHAR szId[(g_dwMaxLenId + 1) * 4];
wcsncpy(szId, pPhone, pEnd - pPhone);
szId[pEnd - pPhone] = L'\0';
// Convert the space separated ids to array
ahtoi(szId, (PWSTR)m_pPhoneId[k / 2].pidPhone);
}
pPhone = pEnd;
}
}
// Build the indexes
if (SUCCEEDED(hr))
{
m_dwPhones /= 2;
// Sort the phone-Id table on phones
qsort(m_pPhoneId, m_dwPhones, sizeof(PHONEMAPNODE), ComparePhone);
// Create an index to search the phone-Id table on Id
m_pIdIdx = (PHONEMAPNODE**)malloc(m_dwPhones * sizeof(PHONEMAPNODE*));
if (!m_pIdIdx)
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
// Initialize with indexes of Id phones in the Phone-Id table
for (k = 0; k < m_dwPhones; k++)
m_pIdIdx[k] = m_pPhoneId + k;
// Sort on Id
qsort(m_pIdIdx, m_dwPhones, sizeof(PHONEMAPNODE*), CompareId);
}
return hr;
} /* CSpPhoneConverter::SetPhoneMap */
/*******************************************************************************
* CSpPhoneConverter::ahtoi *
*--------------------------*
* Description:
* Convert space separated tokens to an array of shorts. This function is
* used by the phone convertor object and the tools
*
* Return:
* n/a
***************************************************************** YUNUSM ******/
void CSpPhoneConverter::ahtoi(WCHAR *pszTokens, // hex numbers as wchar string
WCHAR *pszHexChars // output WCHAR (hex) string
)
{
WCHAR szInput[sizeof(SPPHONEID[g_dwMaxLenId + 1]) * 4];
wcscpy (szInput, pszTokens);
_wcslwr(szInput); // Internally convert this to lower case to make conversion easy
pszTokens = szInput;
*pszHexChars = 0;
int nHexChars = 0;
// Horner's rule
while (*pszTokens)
{
// Convert the token to its numeral form in a WCHAR
WCHAR wHexVal = 0;
bool fFirst = true;
for (int i = 0; i < 4; i++)
{
SPDBG_ASSERT(*pszTokens);
WCHAR k = *pszTokens;
if (k >= L'a')
k = 10 + k - L'a';
else
k -= L'0';
if (fFirst)
fFirst = false;
else
wHexVal *= 16;
wHexVal += k;
pszTokens++;
}
pszHexChars[nHexChars++] = wHexVal;
}
pszHexChars[nHexChars] = 0;
} /* CSpPhoneConverter::ahtoi */
#ifdef SAPI_AUTOMATION
/*****************************************************************************
* CSpPhoneConverter::get_LanguageId *
*--------------------------------------*
*
**************************************************************** Leonro ***/
STDMETHODIMP CSpPhoneConverter::get_LanguageId( SpeechLanguageId* LanguageId )
{
SPDBG_FUNC("CSpPhoneConverter::get_LanguageId");
HRESULT hr = S_OK;
if( SP_IS_BAD_WRITE_PTR( LanguageId ) )
{
hr = E_INVALIDARG;
}
else
{
// Only return the LanguageId if the phoneconv has been initialized with a language
if( m_pPhoneId == NULL )
{
return SPERR_UNINITIALIZED;
}
else
{
*LanguageId = (SpeechLanguageId)m_LangId;
}
}
return hr;
} /* CSpPhoneConverter::get_LanguageId */
/*****************************************************************************
* CSpPhoneConverter::put_LanguageId *
*--------------------------------------*
*
**************************************************************** Leonro ***/
STDMETHODIMP CSpPhoneConverter::put_LanguageId( SpeechLanguageId LanguageId )
{
SPDBG_FUNC("CSpPhoneConverter::put_LanguageId");
HRESULT hr = S_OK;
CComPtr<IEnumSpObjectTokens> cpEnum;
CComPtr<ISpObjectToken> cpPhoneConvToken;
WCHAR szLang[MAX_PATH];
WCHAR szLangCondition[MAX_PATH];
SpHexFromUlong( szLang, LanguageId );
wcscpy( szLangCondition, L"Language=" );
wcscat( szLangCondition, szLang );
// Delete any internal phone to Id tables that have been set previously
if( m_cpObjectToken )
{
delete [] m_pPhoneId;
free(m_pIdIdx);
m_dwPhones = 0;
m_cpObjectToken.Release();
}
// Get the token enumerator
hr = SpEnumTokens( SPCAT_PHONECONVERTERS, szLangCondition, L"VendorPreferred", &cpEnum);
// Get the actual token
if (SUCCEEDED(hr))
{
hr = cpEnum->Next(1, &cpPhoneConvToken, NULL);
if (hr == S_FALSE)
{
cpPhoneConvToken = NULL;
hr = SPERR_NOT_FOUND;
}
}
// Set the token on the PhoneConverter
if( SUCCEEDED( hr ) )
{
hr = SetObjectToken( cpPhoneConvToken );
}
if( SUCCEEDED( hr ) )
{
m_LangId = (LANGID)LanguageId;
}
return hr;
} /* CSpPhoneConverter::put_LanguageId */
/*****************************************************************************
* CSpPhoneConverter::PhoneToId *
*--------------------------------------*
*
**************************************************************** Leonro ***/
STDMETHODIMP CSpPhoneConverter::PhoneToId( const BSTR Phonemes, VARIANT* IdArray )
{
SPDBG_FUNC("CSpPhoneConverter::PhoneToId");
HRESULT hr = S_OK;
int numPhonemes = 0;
if( SP_IS_BAD_STRING_PTR( Phonemes ) )
{
hr = E_INVALIDARG;
}
else if( SP_IS_BAD_WRITE_PTR( IdArray ) )
{
hr = E_POINTER;
}
else
{
SPPHONEID pidArray[SP_MAX_PRON_LENGTH]={0};
hr = PhoneToId( Phonemes, pidArray );
if( SUCCEEDED( hr ) )
{
BYTE *pArray;
numPhonemes = wcslen( pidArray );
SAFEARRAY* psa = SafeArrayCreateVector( VT_I2, 0, numPhonemes );
if( psa )
{
if( SUCCEEDED( hr = SafeArrayAccessData( psa, (void **)&pArray) ) )
{
memcpy(pArray, pidArray, numPhonemes*sizeof(SPPHONEID) );
SafeArrayUnaccessData( psa );
VariantClear(IdArray);
IdArray->vt = VT_ARRAY | VT_I2;
IdArray->parray = psa;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
return hr;
} /* CSpPhoneConverter::PhoneToId */
/*****************************************************************************
* CSpPhoneConverter::IdToPhone *
*--------------------------------------*
*
**************************************************************** Leonro ***/
STDMETHODIMP CSpPhoneConverter::IdToPhone( const VARIANT IdArray, BSTR* Phonemes )
{
SPDBG_FUNC("CSpPhoneConverter::IdToPhone");
HRESULT hr = S_OK;
unsigned short* pIdArray;
if( SP_IS_BAD_WRITE_PTR( Phonemes ) )
{
hr = E_POINTER;
}
else
{
WCHAR pszwPhone[SP_MAX_PRON_LENGTH * (g_dwMaxLenPhone + 1)] = L"";
SPPHONEID* pIds;
hr = VariantToPhoneIds(&IdArray, &pIds);
if( SUCCEEDED( hr ) )
{
hr = IdToPhone( pIds, pszwPhone );
delete pIds;
}
if( SUCCEEDED( hr ) )
{
CComBSTR bstrPhone( pszwPhone );
*Phonemes = bstrPhone.Detach();
}
}
return hr;
} /* CSpPhoneConverter::IdToPhone */
#endif // SAPI_AUTOMATION