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

1803 lines
61 KiB
C++

/*******************************************************************************
* xmlparse.cpp *
*--------------*
* Description:
* This module is the main implementation file for the CSpVoice XML parser.
*-------------------------------------------------------------------------------
* Created By: EDC Date: 12/09/98
* Copyright (C) 1998 Microsoft Corporation
* All Rights Reserved
*
*******************************************************************************/
//--- Additional includes
#include "stdafx.h"
#include "spvoice.h"
#include "commonlx.h"
//--- Local
static const SPLSTR g_Tags[NUM_XMLTAGS] =
{
DEF_SPLSTR( "VOLUME" ),
DEF_SPLSTR( "EMPH" ),
DEF_SPLSTR( "SILENCE" ),
DEF_SPLSTR( "PITCH" ),
DEF_SPLSTR( "RATE" ),
DEF_SPLSTR( "BOOKMARK" ),
DEF_SPLSTR( "PRON" ),
DEF_SPLSTR( "SPELL" ),
DEF_SPLSTR( "LANG" ),
DEF_SPLSTR( "VOICE" ),
DEF_SPLSTR( "CONTEXT" ),
DEF_SPLSTR( "PARTOFSP" ),
DEF_SPLSTR( "SECT" ),
DEF_SPLSTR( "?XML" ),
DEF_SPLSTR( "!--" ),
DEF_SPLSTR( "!DOCTYPE" ),
DEF_SPLSTR( "SAPI" )
};
static const SPLSTR g_Attrs[NUM_XMLATTRS] =
{
DEF_SPLSTR( "ID" ),
DEF_SPLSTR( "SYM" ),
DEF_SPLSTR( "LANGID" ),
DEF_SPLSTR( "LEVEL" ),
DEF_SPLSTR( "MARK" ),
DEF_SPLSTR( "MIDDLE" ),
DEF_SPLSTR( "MSEC" ),
DEF_SPLSTR( "OPTIONAL" ),
DEF_SPLSTR( "RANGE" ),
DEF_SPLSTR( "REQUIRED" ),
DEF_SPLSTR( "SPEED" ),
DEF_SPLSTR( "BEFORE" ),
DEF_SPLSTR( "AFTER" ),
DEF_SPLSTR( "PART" ),
DEF_SPLSTR( "ABSMIDDLE" ),
DEF_SPLSTR( "ABSRANGE" ),
DEF_SPLSTR( "ABSSPEED" )
};
//--- Volume
#define NUM_VOLUME_LEVEL_VALS 4
static const SPLSTR g_VolumeLevelNames[NUM_VOLUME_LEVEL_VALS] =
{
DEF_SPLSTR( "LOUDEST" ),
DEF_SPLSTR( "LOUD" ),
DEF_SPLSTR( "MEDIUM" ),
DEF_SPLSTR( "QUIET" )
};
static const long g_VolumeLevelVals[NUM_VOLUME_LEVEL_VALS] = { 100, 75, 50, 25 };
//--- Part of speech
static const long g_POSLevelVals[] =
{ SPPS_Unknown,
SPPS_Noun,
SPPS_Verb,
SPPS_Modifier,
SPPS_Function,
SPPS_Interjection,
};
#define NUM_POS_LEVEL_VALS sp_countof(g_POSLevelVals)
static const SPLSTR g_POSLevelNames[NUM_POS_LEVEL_VALS] =
{
DEF_SPLSTR( "UNKNOWN" ),
DEF_SPLSTR( "NOUN" ),
DEF_SPLSTR( "VERB" ),
DEF_SPLSTR( "MODIFIER" ),
DEF_SPLSTR( "FUNCTION" ),
DEF_SPLSTR( "INTERJECTION" )
};
//--DEF_SPLSTR( "- Rate
#define NUM_RATE_SPEED_VALS 5
static const SPLSTR g_RateSpeedNames[NUM_RATE_SPEED_VALS] =
{
DEF_SPLSTR( "FASTEST" ),
DEF_SPLSTR( "FAST" ),
DEF_SPLSTR( "MEDIUM" ),
DEF_SPLSTR( "SLOW" ),
DEF_SPLSTR( "SLOWEST" )
};
static const long g_RateSpeedVals[NUM_RATE_SPEED_VALS] = { 10, 5, 0, -5, -10 };
//--- Emphasis
#define NUM_EMPH_LEVEL_VALS 4
static const SPLSTR g_EmphLevelNames[NUM_EMPH_LEVEL_VALS] =
{
DEF_SPLSTR( "STRONG" ),
DEF_SPLSTR( "MODERATE" ),
DEF_SPLSTR( "NONE" ),
DEF_SPLSTR( "REDUCED" )
};
static const long g_EmphLevelVals[NUM_EMPH_LEVEL_VALS] = { 2, 1, 0, -1 };
//--- Pitch
#define NUM_PITCH_VALS 6
static const SPLSTR g_PitchNames[NUM_PITCH_VALS] =
{
DEF_SPLSTR( "HIGHEST" ),
DEF_SPLSTR( "HIGH" ),
DEF_SPLSTR( "MEDIUM" ),
DEF_SPLSTR( "LOW" ),
DEF_SPLSTR( "LOWEST" ),
DEF_SPLSTR( "DEFAULT" )
};
static const long g_PitchVals[NUM_PITCH_VALS] = { 10, 5, 0, -5, -10, 0 };
/*****************************************************************************
* wcatol *
*--------*
* Converts the specified string into a long value. This function
* returns the number of digits converted.
********************************************************************* EDC ***/
ULONG wcatol( WCHAR* pStr, long* pVal, bool fForceHex )
{
SPDBG_ASSERT( pVal && pStr );
long Sign = 1, Val = 0;
ULONG NumConverted;
pStr = wcskipwhitespace( pStr );
WCHAR* pStart = pStr;
//--- Check for a sign specification and skip any whitespace between sign and number
if( *pStr == L'+' )
{
pStr = wcskipwhitespace( ++pStr );
}
else if( *pStr == L'-' )
{
Sign = -1;
pStr = wcskipwhitespace( ++pStr );
}
if( fForceHex || (( pStr[0] == L'0' ) && ( wctoupper( pStr[1] ) == L'X' )) )
{
//--- Start hex conversion
pStr += 2;
pStart = pStr;
do
{
WCHAR wcDigit = wctoupper(*pStr);
if( ( wcDigit >= L'0' ) && ( wcDigit <= L'9' ) )
{
Val *= 16;
Val += wcDigit - L'0';
}
else if( ( wcDigit >= L'A' ) && ( wcDigit <= L'F' ) )
{
Val *= 16;
Val += (wcDigit - L'A') + 10;
}
else
{
break; // end of conversion
}
} while( *(++pStr) );
}
else
{
//--- Start decimal conversion
while( ( *pStr >= L'0' ) && ( *pStr <= L'9' ) )
{
Val *= 10;
Val += *pStr - L'0';
++pStr;
}
}
Val *= Sign;
//--- Tell the caller whether there was any conversion done
NumConverted = ULONG(pStr - pStart);
//--- Return final value
*pVal = Val;
return NumConverted;
} /* wcatol */
/*****************************************************************************
* DoCharSubst *
*-------------*
* Description:
* This method performs XML control character substitution in the
* specified string. Note: It is safe to look beyond pStr to do a match
* because a NULL or a begin tag will cause the comparison to terminate early.
********************************************************************* EDC ***/
void DoCharSubst( WCHAR* pStr, WCHAR* pEnd )
{
int i;
while( pStr < pEnd )
{
if( pStr[0] == L'&' )
{
if( (pEnd - pStr >= 3) && (wctoupper( pStr[2] ) == L'T') )
{
if( wctoupper( pStr[1] ) == L'L' )
{
*pStr++ = L'<';
for( i = 0; i < 2; ++i ) *pStr++ = SP_ZWSP;
}
else if( wctoupper( pStr[1] ) == L'G' )
{
*pStr++ = L'>';
for( i = 0; i < 2; ++i ) *pStr++ = SP_ZWSP;
}
else
{
++pStr;
}
}
else if( (pEnd - pStr >= 4) &&
( wctoupper( pStr[1] ) == L'A' ) &&
( wctoupper( pStr[2] ) == L'M' ) &&
( wctoupper( pStr[3] ) == L'P' ) )
{
*pStr++ = L'&';
for( i = 0; i < 3; ++i ) *pStr++ = SP_ZWSP;
}
else if ( (pEnd - pStr >= 5) &&
( wctoupper( pStr[1] ) == L'A' ) &&
( wctoupper( pStr[2] ) == L'P' ) &&
( wctoupper( pStr[3] ) == L'O' ) &&
( wctoupper( pStr[4] ) == L'S' ) )
{
*pStr++ = L'\'';
for( i = 0; i < 4; ++i ) *pStr++ = SP_ZWSP;
}
else if ( (pEnd - pStr >= 5) &&
( wctoupper( pStr[1] ) == L'Q' ) &&
( wctoupper( pStr[2] ) == L'U' ) &&
( wctoupper( pStr[3] ) == L'O' ) &&
( wctoupper( pStr[4] ) == L'T' ) )
{
*pStr++ = L'\"';
for( i = 0; i < 4; ++i ) *pStr++ = SP_ZWSP;
}
else if ( ( pStr[1] == L'#' ) && ( pStr[2] == L'x' ) )
{
*pStr++ = SP_ZWSP;
pStr[0] = L'0';
long Val, Num;
Num = wcatol( pStr, &Val, false );
if ( Val > 65535 ) Val = 65535;
pStr[0] = (WCHAR)Val;
pStr++;
for( i = 0; i < Num + 1; ++i ) *pStr++ = SP_ZWSP;
}
else
{
++pStr;
}
}
else
{
++pStr;
}
}
} /* DoCharSubst */
/*****************************************************************************
* LookupNamedVal *
*----------------*
* If the function succeeds the return value is "true"
********************************************************************* EDC ***/
HRESULT LookupNamedVal( const SPLSTR* Names, const long* Vals, int Count,
const SPLSTR* pLookFor, long* pVal )
{
int i;
//--- Convert attribute label to upper case
for( i = 0; i < pLookFor->Len; ++i )
{
pLookFor->pStr[i] = wctoupper( pLookFor->pStr[i] );
}
//--- Compare
for( i = 0; i < Count; ++i )
{
if( ( pLookFor->Len == Names[i].Len ) &&
!wcsncmp( pLookFor->pStr, Names[i].pStr, Names[i].Len ) )
{
*pVal = Vals[i];
break;
}
}
return ( i != Count )?( S_OK ):( SPERR_XML_BAD_SYNTAX );
} /* LookupNamedVal */
/*****************************************************************************
* FindAttrVal *
*-------------*
* Description:
* This method starts at the current text position and parses the tag
* to find the next attribute value string.
********************************************************************* EDC ***/
HRESULT FindAttrVal( WCHAR* pStart, WCHAR** ppAttrVal, int* pLen, WCHAR** ppNext )
{
HRESULT hr = SPERR_XML_BAD_SYNTAX;
*ppAttrVal = NULL;
*pLen = 0;
pStart = wcskipwhitespace( pStart );
if( *pStart == L'=' )
{
pStart = wcskipwhitespace( ++pStart );
if( ( *pStart == L'"' ) || ( *pStart == L'\'' ) )
{
WCHAR* pEndTag = wcschr( pStart+1, L'>' );
if( pEndTag )
{
WCHAR* pEnd = pStart+1;
while( ( *pEnd != *pStart ) && ( pEndTag > pEnd ) )
{
++pEnd;
}
if( *pEnd == *pStart )
{
*ppAttrVal = wcskipwhitespace( ++pStart );
*pLen = (int)((wcskiptrailingwhitespace( pEnd - 1 ) + 1) - *ppAttrVal);
*ppNext = pEnd + 1;
hr = S_OK;
}
}
}
}
return hr;
} /* FindAttrVal */
/*****************************************************************************
* FindAttr *
*----------*
* Description:
* This method starts at the current text position and parses the tag
* into to find the next attribute.
*
* S_OK = recognized attribute returned
* S_FALSE = unrecognized attribute skipped
* SPERR_XML_BAD_SYNTAX = syntax error
********************************************************************* EDC ***/
HRESULT FindAttr( WCHAR* pStart, XMLATTRID* peAttr, WCHAR** ppNext )
{
HRESULT hr = S_OK;
WCHAR* pNext = NULL;
//--- Advance past whitespace
pStart = wcskipwhitespace( pStart );
//--- Find end of tag
WCHAR* pEndTag = wcschr( pStart+1, L'>' );
if ( pEndTag )
{
//--- Convert token toupper case
WCHAR* pEndToken = pStart;
while( pEndToken < pEndTag && ( *pEndToken != L'=' ) && !wcisspace( *pEndToken ) )
{
*pEndToken++ = wctoupper( *pEndToken );
}
if ( pEndToken == pEndTag )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
//--- Compare
int j;
for( j = 0; j < NUM_XMLATTRS; ++j )
{
pNext = pStart + g_Attrs[j].Len;
if( !wcsncmp( pStart, g_Attrs[j].pStr, g_Attrs[j].Len ) &&
( wcisspace( *pNext ) || ( *pNext == L'=' ) )
)
{
//--- Found valid attribute
*peAttr = (XMLATTRID)j;
break;
}
}
//--- Skip unknown attribute
if( j == NUM_XMLATTRS )
{
//--- Advance past the attributes value
WCHAR* pEndVal;
int LenVal;
hr = FindAttrVal( pEndToken, &pEndVal, &LenVal, &pNext );
if( hr == S_OK )
{
hr = S_FALSE;
}
}
if( SUCCEEDED( hr ) )
{
*ppNext = pNext;
}
}
}
else
{
hr = SPERR_XML_BAD_SYNTAX;
}
return hr;
} /* FindAttr */
/*****************************************************************************
* FindAfterTagPos *
*-----------------*
* Description:
* This method starts at the current text position and parses the tag
* to find the next character position after the tag.
*
********************************************************************* EDC ***/
WCHAR* FindAfterTagPos( WCHAR* pStart )
{
long lBracketCount = 1;
while( *pStart )
{
if( *pStart == L'<' )
{
++lBracketCount;
}
else if( *pStart == L'>' )
{
--lBracketCount;
}
++pStart;
if( lBracketCount == 0 )
{
break;
}
}
return pStart;
} /* FindAfterTagPos */
/*****************************************************************************
* ParseTag *
*----------*
* Description:
* This method starts at the current text position and parses the tag
* into it's pieces. It returns the position of the next character after
* the tag in the buffer.
*
* S_OK = Tag was recognized and parsed successfully
* SPERR_XML_BAD_SYNTAX = Syntax error
********************************************************************* EDC ***/
HRESULT ParseTag( WCHAR* pStr, XMLTAG* pTag, WCHAR** ppNext )
{
SPDBG_FUNC( "ParseTag" );
HRESULT hr = S_OK;
int i;
pTag->fIsGlobal = false;
pTag->fIsStartTag = true;
pTag->NumAttrs = 0;
//--- Validate that we have a whole tag to try and parse
pStr = wcskipwhitespace( pStr );
if( *pStr == L'<' )
{
if( !wcschr( pStr, L'>' ) )
{
//--- Missing tag end bracket
return SPERR_XML_BAD_SYNTAX;
}
}
else
{
//--- Simple text block, ppNext is next tag or end of string
if( (*ppNext = wcschr( pStr, L'<' )) == NULL )
{
*ppNext = wcschr( pStr, 0 );
}
DoCharSubst( pStr, *ppNext );
pTag->eTag = TAG_TEXT;
return S_OK;
}
//--- Advance past opening bracket and check if this is an end tag
pStr = wcskipwhitespace( ++pStr );
if( *pStr == L'/' )
{
++pStr;
pTag->fIsStartTag = false;
}
//--- Convert tag token to upper case
WCHAR* pUpper = pStr;
while( !wcisspace( *pUpper ) && ( *pUpper != L'>' ) )
{
*pUpper++ = wctoupper( *pUpper );
}
//--- Compare tags
for( i = 0; i < NUM_XMLTAGS; ++i )
{
WCHAR* pNext = pStr + g_Tags[i].Len;
if( !wcsncmp( pStr, g_Tags[i].pStr, g_Tags[i].Len ) &&
( wcisspace( *pNext ) || ( *pNext == L'/' ) || ( *pNext == L'>' ) ) )
{
pTag->eTag = (XMLTAGID)i;
pTag->NumAttrs = 0;
if( *pNext == L'>' )
{
//--- Just point past end
pStr = pNext + 1;
}
else if( ( pTag->eTag == TAG_XMLCOMMENT ) ||
( pTag->eTag == TAG_XMLDOC ) ||
( pTag->eTag == TAG_XMLDOCTYPE ) )
{
pStr = FindAfterTagPos( pStr );
}
else //--- Get tag attributes
{
pStr = pNext;
while( ( hr == S_OK ) && *pStr )
{
//--- Check for tag termination
pStr = wcskipwhitespace( pStr );
if( *pStr == L'/' )
{
++pStr;
pTag->fIsGlobal = true;
pStr = wcskipwhitespace( pStr );
}
//--- End of tag
if( *pStr == L'>' )
{
++pStr;
break;
}
//--- Get next attribute name/val pair
WCHAR* pAttr = pStr;
hr = FindAttr( pStr, &pTag->Attrs[pTag->NumAttrs].eAttr, &pStr );
if( hr == S_OK )
{
hr = FindAttrVal( pStr, &pTag->Attrs[pTag->NumAttrs].Value.pStr,
&pTag->Attrs[pTag->NumAttrs].Value.Len, &pStr );
if( hr == S_OK )
{
++pTag->NumAttrs;
}
}
else if( hr == S_FALSE )
{
hr = S_OK;
#ifdef _DEBUG
WCHAR Buff[100];
ULONG i;
for( i = 0;
( i < sp_countof( Buff )) && ( pAttr[i] != 0 ) &&
( pAttr[i] != L' ' ); ++i )
{
Buff[i] = pAttr[i];
}
Buff[i] = 0;
SPDBG_DMSG1( "\nUnknown attribute ignored => %s\n", Buff );
#endif
}
}
}
//--- Exit tag search loop
break;
}
}
if( SUCCEEDED( hr ) )
{
//--- If no match, mark as unknown so it can be skipped
if( i == NUM_XMLTAGS )
{
pStr = FindAfterTagPos( pStr );
pTag->eTag = TAG_UNKNOWN;
}
*ppNext = pStr;
}
return hr;
} /* CSpVoice::ParseTag */
/*****************************************************************************
* QueryVoiceAttributes *
*----------------------*
* This returns a composite attribute string for the specified voice
********************************************************************* EDC ***/
HRESULT QueryVoiceAttributes( ISpTTSEngine* pVoice, WCHAR** ppAttrs )
{
HRESULT hr = S_OK;
static const SPLSTR ValKeys[] =
{
DEF_SPLSTR( "Language" ),
DEF_SPLSTR( "Gender" ),
DEF_SPLSTR( "Name" ),
DEF_SPLSTR( "Vendor" )
};
CComQIPtr<ISpObjectWithToken> cpObjWithToken( pVoice );
CComPtr<ISpObjectToken> cpObjToken;
hr = cpObjWithToken->GetObjectToken( &cpObjToken );
if( SUCCEEDED( hr ) )
{
CComPtr<ISpDataKey> cpDataKey;
hr = cpObjToken->OpenKey( KEY_ATTRIBUTES, &cpDataKey );
if( SUCCEEDED( hr ) )
{
WCHAR* Composite = (WCHAR*)alloca( MAX_PATH * sizeof(WCHAR) );
Composite[0] = 0;
ULONG CompLen = 0;
//--- We loop through all of the attributes even if they are not found
for( ULONG i = 0; i < sp_countof(ValKeys); ++i )
{
WCHAR* pcomemVal;
hr = cpDataKey->GetStringValue( ValKeys[i].pStr, &pcomemVal );
if( hr == S_OK )
{
if( CompLen + (ValKeys[i].Len + 1) < MAX_PATH )
{
wcscat( wcscat( Composite, ValKeys[i].pStr ), L"=" );
CompLen += ValKeys[i].Len + 1;
}
ULONG Len = wcslen( pcomemVal );
if( CompLen + (Len + 1) < MAX_PATH )
{
wcscat( wcscat( Composite, pcomemVal ), L";" );
CompLen += Len + 1;
}
::CoTaskMemFree( pcomemVal );
}
}
//--- The search is always okay
hr = S_OK;
if( CompLen )
{
ULONG NumChars = CompLen+1;
*ppAttrs = new WCHAR[NumChars];
if( *ppAttrs )
{
memcpy( *ppAttrs, Composite, NumChars * sizeof(WCHAR) );
}
}
else
{
//--- The voice had no attributes, this should never happen
SPDBG_ASSERT(0);
*ppAttrs = NULL;
}
}
}
return hr;
} /* QueryVoiceAttributes */
/*****************************************************************************
* GetVoiceAttr *
*--------------*
* This finds the specified attribute, NULL terminates it in the buffer,
* and returns a pointer to it.
********************************************************************* EDC ***/
WCHAR* GetVoiceAttr( XMLTAG& Tag, XMLATTRID eAttr )
{
//--- Setup pointers to attributes
LPWSTR pAttr = NULL;
for( int i = 0; i < Tag.NumAttrs; ++i )
{
if( Tag.Attrs[i].eAttr == eAttr )
{
pAttr = Tag.Attrs[i].Value.pStr;
pAttr[Tag.Attrs[i].Value.Len] = 0;
break;
}
}
return pAttr;
} /* GetVoiceAttr */
/*****************************************************************************
* CSpVoice::FindToken *
*---------------------*
* This method finds the best matching object token
********************************************************************* EDC ***/
HRESULT FindToken( XMLTAG& Tag, const WCHAR* pCat,
WCHAR* pCurrVoiceAttrs, ISpObjectToken** ppTok )
{
SPDBG_FUNC( "FindToken" );
SPDBG_ASSERT( ppTok && ( *ppTok == NULL ) );
HRESULT hr = S_OK;
//--- Compose the optional attributes
WCHAR* pOpt = GetVoiceAttr( Tag, ATTR_OPTIONAL );
if( pOpt )
{
WCHAR* pNew = (WCHAR*)alloca( (wcslen(pOpt) + wcslen(pCurrVoiceAttrs) + 1) * sizeof(WCHAR) );
wcscat( wcscat( wcscpy( pNew, pOpt ), L";" ), pCurrVoiceAttrs );
pOpt = pNew;
}
else
{
pOpt = pCurrVoiceAttrs;
}
//--- Find token based on attributes
CComPtr<IEnumSpObjectTokens> cpEnum;
hr = SpEnumTokens( pCat, GetVoiceAttr( Tag, ATTR_REQUIRED ), pOpt, &cpEnum );
if( SUCCEEDED(hr) )
{
hr = cpEnum->Next( 1, ppTok, NULL );
}
if( hr == S_FALSE )
{
hr = SPERR_XML_RESOURCE_NOT_FOUND;
}
return hr;
} /* FindToken */
/*****************************************************************************
* SetXMLVoice *
*-------------*
* This sets the current voice. It has been moved to a separate function
* so that the temp memory from alloca is freed on each iteration.
********************************************************************* EDC ***/
HRESULT CSpVoice::
SetXMLVoice( XMLTAG& Tag, CVoiceNode* pVoiceNode, CPhoneConvNode* pPhoneConvNode )
{
HRESULT hr = S_OK;
if( Tag.fIsStartTag )
{
WCHAR* pCurrVoiceAttrs = (m_GlobalStateStack.GetVal()).pVoiceEntry->m_pAttrs;
CComPtr<ISpObjectToken> cpObjToken;
hr = FindToken( Tag, SPCAT_VOICES, pCurrVoiceAttrs, &cpObjToken );
//--- See if we already have the specified voice loaded
CSpDynamicString dstrDesiredId;
if( SUCCEEDED( hr ) )
{
hr = cpObjToken->GetId( &dstrDesiredId.m_psz );
}
CVoiceNode* pLastNode = NULL;
GLOBALSTATE NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.cpPhoneConverter = NULL;
NewGlobalState.cpVoice = NULL;
NewGlobalState.pVoiceEntry = NULL;
if( SUCCEEDED( hr ) )
{
while( pVoiceNode )
{
//--- Do case insensitive comparison of IDs
if( !_wcsicmp( pVoiceNode->m_dstrVoiceTokenId.m_psz, dstrDesiredId.m_psz ) )
{
NewGlobalState.pVoiceEntry = pVoiceNode;
NewGlobalState.cpVoice = pVoiceNode->m_cpVoice;
break;
}
pLastNode = pVoiceNode;
pVoiceNode = pVoiceNode->m_pNext;
}
}
//--- Create new voice if we didn't find it in the voice list
if( SUCCEEDED( hr ) && !NewGlobalState.cpVoice )
{
hr = SpCreateObjectFromToken( cpObjToken, &NewGlobalState.cpVoice );
//--- Add to cache list
if( SUCCEEDED( hr ) )
{
pLastNode->m_pNext = new CVoiceNode;
if( pLastNode->m_pNext )
{
NewGlobalState.pVoiceEntry = pLastNode->m_pNext;
NewGlobalState.pVoiceEntry->m_cpVoice = NewGlobalState.cpVoice;
NewGlobalState.pVoiceEntry->m_dstrVoiceTokenId = dstrDesiredId;
hr = QueryVoiceAttributes( NewGlobalState.cpVoice,
&NewGlobalState.pVoiceEntry->m_pAttrs );
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
//--- Add new voice to stack
if( SUCCEEDED( hr ) )
{
//--- Now need to add a new phone converter corresponding to the new voice
LANGID langid;
hr = SpGetLanguageFromVoiceToken(cpObjToken, &langid);
if (SUCCEEDED(hr))
{
//--- See if we already have this phone converter loaded
LANGID ExistingLangId = pPhoneConvNode->m_LangID;
CPhoneConvNode *pLastPhoneConvNode = NULL;
while ( pPhoneConvNode )
{
if ( ExistingLangId == langid )
{
NewGlobalState.cpPhoneConverter = pPhoneConvNode->m_cpPhoneConverter;
break;
}
pLastPhoneConvNode = pPhoneConvNode;
pPhoneConvNode = pPhoneConvNode->m_pNext;
}
if ( !NewGlobalState.cpPhoneConverter )
{
//--- Didn't find the phone converter in the list
hr = SpCreatePhoneConverter(langid, NULL, NULL, &NewGlobalState.cpPhoneConverter);
if ( SUCCEEDED( hr ) )
{
pLastPhoneConvNode->m_pNext = new CPhoneConvNode;
if ( pLastPhoneConvNode->m_pNext )
{
pLastPhoneConvNode->m_pNext->m_cpPhoneConverter =
NewGlobalState.cpPhoneConverter;
pLastPhoneConvNode->m_pNext->m_LangID = langid;
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
//--- Add new stuff to the stacks
if ( SUCCEEDED( hr ) )
{
hr = m_GlobalStateStack.SetVal( NewGlobalState, true );
}
}
//--- end section
}
}
else
{
hr = PopXMLState();
}
return hr;
} /* CSpVoice::SetXMLVoice */
/*****************************************************************************
* SetXMLLanguage *
*----------------*
* This sets the current Locale, which can change the current voice. It has
* been moved to a separate function so that the temp memory from alloca is
* freed on each iteration.
********************************************************************* AH ****/
HRESULT CSpVoice::SetXMLLanguage( XMLTAG& Tag, CVoiceNode* pVoiceNode,
CPhoneConvNode* pPhoneConvNode )
{
HRESULT hr = S_OK;
long Value = 0;
if( Tag.fIsStartTag )
{
//--- Find the LANGID attribute
for( int AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_LANGID ) break;
}
if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0) )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
//--- Get lang id, which is spec'ed as hex without the leading 0x??
wcatol( Tag.Attrs[AttrIndex].Value.pStr, &Value, true );
//--- Compose the required string
WCHAR Required[30];
wcsncat( wcscpy( Required, L"Language=" ), Tag.Attrs[AttrIndex].Value.pStr,
min( Tag.Attrs[AttrIndex].Value.Len, sp_countof( Required )) );
/* We'll create a voice selection tag with the LANGID being required
* and the attributes of the current voice being optional to get the
* closest match.
*/
XMLTAG NewVoiceTag;
memset( &NewVoiceTag, 0, sizeof(XMLTAG) );
NewVoiceTag.fIsGlobal = Tag.fIsGlobal;
NewVoiceTag.fIsStartTag = Tag.fIsStartTag;
NewVoiceTag.eTag = TAG_VOICE;
NewVoiceTag.NumAttrs = 2;
NewVoiceTag.Attrs[0].eAttr = ATTR_REQUIRED;
NewVoiceTag.Attrs[0].Value.pStr = Required;
NewVoiceTag.Attrs[0].Value.Len = wcslen( Required );
NewVoiceTag.Attrs[1].eAttr = ATTR_OPTIONAL;
NewVoiceTag.Attrs[1].Value.pStr = m_GlobalStateStack.GetVal().pVoiceEntry->m_pAttrs;
NewVoiceTag.Attrs[1].Value.Len = wcslen( NewVoiceTag.Attrs[1].Value.pStr );
//--- Try to set a new voice
hr = SetXMLVoice( NewVoiceTag, pVoiceNode, pPhoneConvNode );
//--- If no voice matches request, we just set the new langid
// and let the current engine do its best.
if( hr == SPERR_XML_RESOURCE_NOT_FOUND )
{
hr = S_OK;
GLOBALSTATE NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.LangID = (LANGID)Value;
hr = m_GlobalStateStack.SetVal( NewGlobalState, true );
}
}
}
else
{
hr = PopXMLState();
}
return hr;
} /* CSpVoice::SetXMLLanguage */
/*****************************************************************************
* CSpVoice::ConvertPhonStr2Bin *
*------------------------------*
* Description:
* This method converts the alpha phoneme string to binary
* in place.
********************************************************************* EDC ***/
HRESULT CSpVoice::ConvertPhonStr2Bin( XMLTAG& Tag, int AttrIndex, SPVTEXTFRAG* pFrag )
{
HRESULT hr = S_OK;
pFrag->State.eAction = SPVA_Pronounce;
Tag.Attrs[AttrIndex].Value.pStr[Tag.Attrs[AttrIndex].Value.Len] = 0;
hr = (m_GlobalStateStack.GetVal()).cpPhoneConverter->
PhoneToId( Tag.Attrs[AttrIndex].Value.pStr, Tag.Attrs[AttrIndex].Value.pStr );
if( SUCCEEDED(hr) )
{
pFrag->State.pPhoneIds = Tag.Attrs[AttrIndex].Value.pStr;
pFrag->State.pPhoneIds[wcslen(Tag.Attrs[AttrIndex].Value.pStr)] = 0;
}
return hr;
} /* CSpVoice::ConvertPhonStr2Bin */
/*****************************************************************************
* CSpVoice::ParseXML *
*--------------------*
* Description:
* This method parses the text buffer and creates the text block array
* for the specified render info structure. The voice has a global document
* concept. You are always in an XML doc. The parser allows a single level
* of XML document to be nested within the global document.
********************************************************************* EDC ***/
HRESULT CSpVoice::ParseXML( CSpeakInfo& SI )
{
SPDBG_FUNC( "CSpVoice::ParseXML" );
SPDBG_ASSERT( SI.m_pSpeechSegList == NULL );
HRESULT hr = S_OK;
LPWSTR pPos, pNext = SI.m_pText;
SPVTEXTFRAG* pFrag;
CSpeechSeg* pCurrSeg = NULL;
GLOBALSTATE NewGlobalState, SavedState;
XMLTAG Tag;
long Val;
int AttrIndex;
//--- Save state unless caller wants to persist
if( !(SI.m_dwSpeakFlags & SPF_PERSIST_XML) )
{
SavedState = m_GlobalStateStack.GetBaseVal();
}
//=== Initialize Voice usage list, this is only used during parsing
CVoiceNode VoiceList;
(m_GlobalStateStack.GetValRef()).pVoiceEntry = &VoiceList;
VoiceList.m_cpVoice = (m_GlobalStateStack.GetVal()).cpVoice;
hr = m_cpVoiceToken->GetId( &VoiceList.m_dstrVoiceTokenId.m_psz );
if( SUCCEEDED( hr ) )
{
hr = QueryVoiceAttributes( VoiceList.m_cpVoice, &VoiceList.m_pAttrs );
}
//=== Initialize Phone converter usage list, this is only used during parsing
CPhoneConvNode PhoneConvList;
if( SUCCEEDED( hr ) )
{
CComPtr<ISpObjectWithToken> cpObjWithToken;
CComPtr<ISpObjectToken> cpObjToken;
CSpDynamicString dstrAttributes;
PhoneConvList.m_cpPhoneConverter = (m_GlobalStateStack.GetBaseVal()).cpPhoneConverter;
hr = PhoneConvList.m_cpPhoneConverter.QueryInterface( &cpObjWithToken );
if ( SUCCEEDED ( hr ) )
{
hr = cpObjWithToken->GetObjectToken( &cpObjToken );
if ( SUCCEEDED( hr ) )
{
LANGID langid;
hr = SpGetLanguageFromVoiceToken(cpObjToken, &langid);
if (SUCCEEDED(hr))
{
PhoneConvList.m_LangID = langid;
}
}
}
}
//--- Main parsing loop
while( *pNext && ( hr == S_OK ) )
{
pPos = wcskipwhitespace( pNext );
if( ( hr = ParseTag( pPos, &Tag, &pNext )) == S_OK )
{
switch( Tag.eTag )
{
//--- Use the current state to add a text info block to list
case TAG_UNKNOWN:
case TAG_TEXT:
{
if( !pCurrSeg )
{
hr = SI.AddNewSeg( GetCurrXMLVoice(), &pCurrSeg );
}
if( SUCCEEDED( hr ) )
{
//--- Add the text fragment
pFrag = pCurrSeg->AddFrag( this, SI.m_pText, pPos, pNext );
if( pFrag && ( Tag.eTag == TAG_UNKNOWN ) )
{
pFrag->State.eAction = SPVA_ParseUnknownTag;
}
}
}
break;
//--- Change voice --------------------------------------
case TAG_VOICE:
{
//--- Set the new voice
hr = SetXMLVoice( Tag, &VoiceList, &PhoneConvList );
//--- Set the current seg to NULL to force a new segment to be created
pCurrSeg = NULL;
}
break;
//--- Set context --------------------------------------
case TAG_CONTEXT:
{
if( Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else if( Tag.fIsStartTag )
{
SPVCONTEXT Ctx = (m_GlobalStateStack.GetVal()).Context;
for( int i = 0; i < Tag.NumAttrs; ++i )
{
if( Tag.Attrs[i].eAttr == ATTR_ID )
{
Ctx.pCategory = Tag.Attrs[i].Value.pStr;
}
else if( Tag.Attrs[i].eAttr == ATTR_BEFORE )
{
Ctx.pBefore = Tag.Attrs[i].Value.pStr;
}
else if( Tag.Attrs[i].eAttr == ATTR_AFTER )
{
Ctx.pAfter = Tag.Attrs[i].Value.pStr;
}
else
{
continue;
}
//--- Terminate buffer strings
Tag.Attrs[i].Value.pStr[Tag.Attrs[i].Value.Len] = 0;
}
NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.Context = Ctx;
hr = m_GlobalStateStack.SetVal( NewGlobalState, !Tag.fIsGlobal );
}
else
{
hr = PopXMLState();
}
}
break;
//--- Volume ------------------------------------------------------------
case TAG_VOLUME:
{
if( Tag.fIsStartTag )
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_LEVEL ) break;
}
if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0))
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
ULONG NumConv = wcatol( Tag.Attrs[AttrIndex].Value.pStr, &Val, false );
if( NumConv == 0 )
{
hr = LookupNamedVal( g_VolumeLevelNames, g_VolumeLevelVals,
NUM_VOLUME_LEVEL_VALS,
&Tag.Attrs[AttrIndex].Value,
&Val );
if( hr != S_OK )
{
hr = SPERR_XML_BAD_SYNTAX;
break;
}
}
else if( NumConv != (ULONG)Tag.Attrs[AttrIndex].Value.Len )
{
hr = SPERR_XML_BAD_SYNTAX;
break;
}
if( SUCCEEDED( hr ) )
{
Val = max( min( Val, 100 ), 0 );
NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.Volume = Val;
hr = m_GlobalStateStack.SetVal( NewGlobalState, !Tag.fIsGlobal );
}
}
}
else
{
hr = PopXMLState();
}
}
break;
//--- Emphasis ----------------------------------------------------------
case TAG_EMPH:
{
if( Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else if( Tag.fIsStartTag )
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_LEVEL ) break;
}
if( Tag.NumAttrs == 0 )
{
NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.EmphAdj = g_EmphLevelVals[1];
hr = m_GlobalStateStack.SetVal( NewGlobalState, !Tag.fIsGlobal );
}
else if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0) )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
ULONG NumConv = wcatol( Tag.Attrs[AttrIndex].Value.pStr, &Val, false );
if ( NumConv == 0 )
{
hr = LookupNamedVal( g_EmphLevelNames, g_EmphLevelVals,
NUM_EMPH_LEVEL_VALS, &Tag.Attrs[AttrIndex].Value,
&Val );
}
else if ( NumConv != (ULONG) Tag.Attrs[AttrIndex].Value.Len )
{
hr = SPERR_XML_BAD_SYNTAX;
}
if( SUCCEEDED( hr ) )
{
NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.EmphAdj = Val;
hr = m_GlobalStateStack.SetVal( NewGlobalState, !Tag.fIsGlobal );
}
}
}
else
{
hr = PopXMLState();
}
}
break;
//--- Pitch -------------------------------------------------------------
case TAG_PITCH:
{
if( Tag.fIsStartTag )
{
//--- Make sure we have at least one known attribute
if( Tag.NumAttrs == 0 )
{
hr = SPERR_XML_BAD_SYNTAX;
break;
}
NewGlobalState = m_GlobalStateStack.GetVal();
SPVPITCH Pitch = NewGlobalState.PitchAdj;
for( int i = 0; i < Tag.NumAttrs; ++i )
{
ULONG NumConv = wcatol( Tag.Attrs[i].Value.pStr, &Val, false );
if( NumConv == 0 )
{
hr = LookupNamedVal( g_PitchNames, g_PitchVals, NUM_PITCH_VALS,
&Tag.Attrs[i].Value, &Val );
if( hr != S_OK )
{
hr = SPERR_XML_BAD_SYNTAX;
break;
}
}
else if( NumConv != (ULONG)Tag.Attrs[i].Value.Len )
{
hr = SPERR_XML_BAD_SYNTAX;
break;
}
switch( Tag.Attrs[i].eAttr )
{
case ATTR_MIDDLE:
Pitch.MiddleAdj += Val;
break;
case ATTR_ABSMIDDLE:
Pitch.MiddleAdj = Val;
break;
case ATTR_RANGE:
Pitch.RangeAdj += Val;
break;
case ATTR_ABSRANGE:
Pitch.RangeAdj = Val;
break;
}
}
if ( SUCCEEDED( hr ) )
{
NewGlobalState.PitchAdj = Pitch;
hr = m_GlobalStateStack.SetVal( NewGlobalState, !Tag.fIsGlobal );
}
}
else
{
hr = PopXMLState();
}
}
break;
//--- Rate --------------------------------------------------------------
case TAG_RATE:
{
if( Tag.fIsStartTag )
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( ( Tag.Attrs[AttrIndex].eAttr == ATTR_SPEED ) ||
( Tag.Attrs[AttrIndex].eAttr == ATTR_ABSSPEED ) )
{
ULONG NumConv = wcatol( Tag.Attrs[AttrIndex].Value.pStr, &Val, false );
if( NumConv == 0 )
{
hr = LookupNamedVal( g_RateSpeedNames, g_RateSpeedVals, NUM_RATE_SPEED_VALS,
&Tag.Attrs[AttrIndex].Value, &Val );
if( hr != S_OK )
{
hr = SPERR_XML_BAD_SYNTAX;
break;
}
}
else if( NumConv != (ULONG)Tag.Attrs[AttrIndex].Value.Len )
{
hr = SPERR_XML_BAD_SYNTAX;
break;
}
if( SUCCEEDED( hr ) )
{
NewGlobalState = m_GlobalStateStack.GetVal();
if( Tag.Attrs[AttrIndex].eAttr == ATTR_SPEED )
{
NewGlobalState.RateAdj += Val;
}
else
{
NewGlobalState.RateAdj = Val;
}
hr = m_GlobalStateStack.SetVal( NewGlobalState, !Tag.fIsGlobal );
}
//--- Set Rate flag so that m_fUseDefaultRate can be updated
if ( SUCCEEDED( hr ) &&
Tag.fIsGlobal )
{
if ( !pCurrSeg )
{
hr = SI.AddNewSeg( GetCurrXMLVoice(), &pCurrSeg );
}
if ( SUCCEEDED( hr ) )
{
pCurrSeg->SetRateFlag();
}
}
break;
}
}
if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0))
{
hr = SPERR_XML_BAD_SYNTAX;
}
}
else
{
hr = PopXMLState();
}
}
break;
//--- SPELL -----------------------------------------------------
case TAG_SPELL:
{
if( Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else if( Tag.fIsStartTag )
{
NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.fDoSpellOut = true;
hr = m_GlobalStateStack.SetVal( NewGlobalState, !Tag.fIsGlobal );
}
else
{
hr = PopXMLState();
}
}
break;
//--- Change Lang -------------------------------------------------
case TAG_LANG:
{
//--- Set the current language
hr = SetXMLLanguage( Tag, &VoiceList, &PhoneConvList );
//--- Set the current seg to NULL to force a new segment to be created
pCurrSeg = NULL;
}
break;
//--- Silence -----------------------------------------------------
case TAG_SILENCE:
{
if( !Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_MSEC ) break;
}
if( ( AttrIndex == Tag.NumAttrs ) ||
( Tag.Attrs[AttrIndex].Value.Len <= 0) ||
( !( wcatol( Tag.Attrs[AttrIndex].Value.pStr, &Val, false ) ==
(ULONG) Tag.Attrs[AttrIndex].Value.Len ) ) )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
if( !pCurrSeg )
{
hr = SI.AddNewSeg( GetCurrXMLVoice(), &pCurrSeg );
}
if( SUCCEEDED( hr ) )
{
pFrag = pCurrSeg->AddFrag( this, SI.m_pText, pPos, pNext );
if( pFrag )
{
Val = max( min( Val, 65535 ), 0 );
pFrag->State.eAction = SPVA_Silence;
pFrag->State.SilenceMSecs = Val;
pFrag->pTextStart = NULL;
pFrag->ulTextLen = 0;
}
}
}
}
}
break;
//--- Bookmark ----------------------------------------------------
case TAG_BOOKMARK:
{
if( !Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_MARK ) break;
}
if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0))
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
if( !pCurrSeg )
{
hr = SI.AddNewSeg( GetCurrXMLVoice(), &pCurrSeg );
}
if( SUCCEEDED( hr ) )
{
pFrag = pCurrSeg->AddFrag( this, SI.m_pText, pPos, pNext );
if( pFrag )
{
pFrag->State.eAction = SPVA_Bookmark;
pFrag->pTextStart = Tag.Attrs[AttrIndex].Value.pStr;
pFrag->ulTextLen = Tag.Attrs[AttrIndex].Value.Len;
}
}
}
}
}
break;
//--- Section ----------------------------------------------------
case TAG_SECT:
{
if( !Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_ID ) break;
}
if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0))
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
if( !pCurrSeg )
{
hr = SI.AddNewSeg( GetCurrXMLVoice(), &pCurrSeg );
}
if( SUCCEEDED( hr ) )
{
pFrag = pCurrSeg->AddFrag( this, SI.m_pText, pPos, pNext );
if( pFrag )
{
pFrag->State.eAction = SPVA_Section;
pFrag->pTextStart = Tag.Attrs[AttrIndex].Value.pStr;
pFrag->ulTextLen = Tag.Attrs[AttrIndex].Value.Len;
}
}
}
}
}
break;
//--- Part of speech --------------------------------------------
case TAG_PARTOFSP:
{
if( Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else if( Tag.fIsStartTag )
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_PART ) break;
}
if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0))
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
hr = LookupNamedVal( g_POSLevelNames, g_POSLevelVals,
NUM_POS_LEVEL_VALS, &Tag.Attrs[AttrIndex].Value,
&Val );
}
if ( SUCCEEDED( hr ) )
{
NewGlobalState = m_GlobalStateStack.GetVal();
NewGlobalState.ePartOfSpeech = (SPPARTOFSPEECH) Val;
hr = m_GlobalStateStack.SetVal( NewGlobalState, true );
}
}
else
{
hr = PopXMLState();
}
}
break;
//--- Pronounciation --------------------------------------------
case TAG_PRON:
{
if( Tag.fIsStartTag )
{
//--- Find the attribute
for( AttrIndex = 0; AttrIndex < Tag.NumAttrs; ++AttrIndex )
{
if( Tag.Attrs[AttrIndex].eAttr == ATTR_SYM ) break;
}
if( ( AttrIndex == Tag.NumAttrs ) || ( Tag.Attrs[AttrIndex].Value.Len <= 0))
{
hr = SPERR_XML_BAD_SYNTAX;
}
else
{
if( !pCurrSeg )
{
hr = SI.AddNewSeg( GetCurrXMLVoice(), &pCurrSeg );
}
if( SUCCEEDED( hr ) )
{
pFrag = pCurrSeg->AddFrag( this, SI.m_pText, pPos, pNext );
if( pFrag )
{
hr = ConvertPhonStr2Bin( Tag, AttrIndex, pFrag );
if( Tag.fIsGlobal )
{
pFrag->pTextStart = NULL;
pFrag->ulTextLen = 0;
}
else if( SUCCEEDED( hr ) )
{
//--- Get the contained phrase to which
// this pronounciation applies
pPos = wcskipwhitespace( pNext );
if( ( hr = ParseTag( pPos, &Tag, &pNext )) == S_OK )
{
if( ( Tag.eTag == TAG_TEXT ) && ( pNext > pPos ) )
{
pFrag->ulTextSrcOffset += ULONG(pPos - pFrag->pTextStart);
pFrag->pTextStart = wcskipwhitespace( pPos );
pFrag->ulTextLen = ULONG(( wcskiptrailingwhitespace( pNext - 1 ) - pFrag->pTextStart ) + 1);
}
else if( ( Tag.eTag == TAG_PRON ) && !Tag.fIsStartTag )
{
//--- Empty scope
pFrag->pTextStart = NULL;
pFrag->ulTextLen = 0;
}
else
{
hr = SPERR_XML_BAD_SYNTAX;
}
}
}
} // end if frag
}
}
}
}
break;
//--- Comment ---------------------------------------------------
case TAG_XMLCOMMENT: // skip
case TAG_XMLDOC:
case TAG_XMLDOCTYPE:
break;
//--- New scope -------------------------------------------------
case TAG_SAPI:
{
if( Tag.fIsGlobal )
{
hr = SPERR_XML_BAD_SYNTAX;
}
else if( Tag.fIsStartTag )
{
m_GlobalStateStack.DupAndPushVal();
}
else
{
if( SUCCEEDED( hr = PopXMLState() ) )
{
//--- Add an empty segement with the global voice restored.
// This will cause a change voice event to occur.
hr = SI.AddNewSeg( GetCurrXMLVoice(), &pCurrSeg );
}
}
}
break;
default:
//--- Bad tag
SPDBG_ASSERT( 0 );
} // end switch
} // end if successful tag parsing
} // end while
//--- Close any scopes left open
m_GlobalStateStack.Reset();
//--- Restore state unless caller wants to persist
if( !(SI.m_dwSpeakFlags & SPF_PERSIST_XML) )
{
m_GlobalStateStack.SetBaseVal( SavedState );
}
return hr;
} /* CSpVoice::ParseXML */
//
//=== CSpeechSeg ==============================================================
//
/*****************************************************************************
* CSpeechSeg::AddFrag *
*---------------------*
* Description:
* This method adds a text fragment structure to the current xml parse
* list and initializes it with the current state.
********************************************************************* EDC ***/
SPVTEXTFRAG* CSpeechSeg::
AddFrag( CSpVoice* pVoice, WCHAR* pStart, WCHAR* pPos, WCHAR* pNext )
{
SPVTEXTFRAG* pFrag = new SPVTEXTFRAG;
if( pFrag )
{
//--- Add the fragment
memset( pFrag, 0, sizeof( *pFrag ) );
if( m_pFragTail )
{
m_pFragTail->pNext = pFrag;
m_pFragTail = pFrag;
}
else
{
m_pFragHead = m_pFragTail = pFrag;
}
//--- Initialize it
GLOBALSTATE tempGlobalState = pVoice->m_GlobalStateStack.GetVal();
pFrag->State = (SPVSTATE) tempGlobalState;
pFrag->State.eAction = tempGlobalState.fDoSpellOut ? (SPVA_SpellOut):(SPVA_Speak);
pFrag->pTextStart = wcskipwhitespace( pPos );
pFrag->ulTextLen = ULONG(pNext - pFrag->pTextStart);
pFrag->ulTextSrcOffset = ULONG(pFrag->pTextStart - pStart);
}
return pFrag;
} /* CSpeechSeg::AddFrag */
/****************************************************************************
* CSpeechSeg::Init *
*------------------*
* Description:
*
* Returns:
*
********************************************************************* RAL ***/
HRESULT CSpeechSeg::Init( ISpTTSEngine * pCurrVoice, const CSpStreamFormat & OutFmt )
{
SPDBG_FUNC("CSpeechSeg::Init");
HRESULT hr = S_OK;
if ( m_VoiceFormat.FormatId() != GUID_NULL)
{
m_VoiceFormat.Clear();
}
SPDBG_ASSERT(m_VoiceFormat.FormatId() == GUID_NULL);
hr = pCurrVoice->GetOutputFormat(&OutFmt.FormatId(), OutFmt.WaveFormatExPtr(),
&m_VoiceFormat.m_guidFormatId, &m_VoiceFormat.m_pCoMemWaveFormatEx);
if (SUCCEEDED(hr))
{
m_cpVoice = pCurrVoice;
}
SPDBG_REPORT_ON_FAIL( hr );
return hr;
} /* CSpeechSeg::Init */
//
//=== CSpeakInfo ==============================================================
//
/*****************************************************************************
* CSpeakInfo::AddNewSeg *
*-----------------------*
* Description:
* This method creates a new speech segment. Each segment is intended for
* a different underlying engine voice.
********************************************************************* EDC ***/
HRESULT CSpeakInfo::AddNewSeg( ISpTTSEngine* pCurrVoice, CSpeechSeg** ppNew )
{
HRESULT hr = S_OK;
*ppNew = NULL;
if( m_pSpeechSegListTail && ( m_pSpeechSegListTail->GetFragList() == NULL ) )
{
//--- Reuse empty segment
hr = m_pSpeechSegListTail->Init( pCurrVoice, m_OutStreamFmt );
if (SUCCEEDED(hr))
{
*ppNew = m_pSpeechSegListTail;
}
}
else
{
//--- Create and attach new segment
CSpeechSeg* pNew = new CSpeechSeg;
if( !pNew )
{
hr = E_OUTOFMEMORY;
}
else
{
hr = pNew->Init( pCurrVoice, m_OutStreamFmt );
if (SUCCEEDED(hr))
{
*ppNew = pNew;
if( m_pSpeechSegListTail )
{
m_pSpeechSegListTail->SetNextSeg( pNew );
m_pSpeechSegListTail = pNew;
}
else
{
m_pSpeechSegList = m_pSpeechSegListTail = pNew;
}
}
else
{
delete pNew;
}
}
}
return hr;
} /* CSpeakInfo::AddNewSeg */