|
|
/////////////////////////////////////////////////////////////////////////////
// PromptEng.cpp : Implementation of CPromptEng
//
// Prompt Engine concatenates pre-recorded phrases for voice output, and
// falls back on TTS Engine when pre-recorded phrases are unavailable.
//
// Created by JOEM 01-2000
// Copyright (C) 2000 Microsoft Corporation
// All Rights Reserved
//
//////////////////////////////////////////////////////////// JOEM 01-2000 //
#include "stdafx.h"
#include "MSPromptEng.h"
#include "PromptEng.h"
#include "XMLTag.h"
#include "vapiIO.h"
#include "common.h"
// Text fragments with over 1024 chars should use TTS (no search).
#define MAX_SEARCH_LEN 1024
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::FinalConstruct
//
// Constructor: Creates the Prompt Db object, TTS voice, and local output site
//
//////////////////////////////////////////////////////////// JOEM 01-2000 //
HRESULT CPromptEng::FinalConstruct() { SPDBG_FUNC( "CPromptEng::FinalConstruct" ); HRESULT hr = S_OK;
m_pOutputSite = NULL; m_dQueryCost = 0.0; m_fAbort = false;
if( SUCCEEDED( hr ) ) { hr = m_cpPromptDb.CoCreateInstance(CLSID_PromptDb); } if ( SUCCEEDED( hr ) ) { m_pOutputSite = new CLocalTTSEngineSite; if ( !m_pOutputSite ) { hr = E_OUTOFMEMORY; } }
if ( FAILED( hr ) ) { if ( m_cpPromptDb ) { m_cpPromptDb.Release(); } if ( m_pOutputSite ) { m_pOutputSite->Release(); } }
SPDBG_REPORT_ON_FAIL( hr ); return hr;
} /* CPromptEng::FinalConstruct */
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::FinalRelease
//
// Destructor
//
//////////////////////////////////////////////////////////// JOEM 01-2000 //
void CPromptEng::FinalRelease() { SPDBG_FUNC( "CPromptEng::FinalRelease" ); USHORT i = 0;
if ( m_cpPromptDb ) { m_cpPromptDb.Release(); }
if ( m_cpTTSEngine ) { m_cpTTSEngine.Release(); }
if ( m_pOutputSite ) { m_pOutputSite->Release(); }
for ( i=0; i<m_apQueries.GetSize(); i++ ) { if ( m_apQueries[i] ) { delete m_apQueries[i]; m_apQueries[i] = NULL; } } m_apQueries.RemoveAll();
} /* CPromptEng::FinalRelease */
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::SetObjectToken
//
// Get all of the relevant registry information for this voice
//
//////////////////////////////////////////////////////////// JOEM 02-2000 //
STDMETHODIMP CPromptEng::SetObjectToken(ISpObjectToken * pToken) { SPDBG_FUNC( "CPromptEng::SetObjectToken" ); HRESULT hr = S_OK; ISpDataKey* pDataKey = NULL; WCHAR pszKey[USHRT_MAX] = L""; USHORT i = 0; CSpDynamicString dstrPath; CSpDynamicString dstrName; CSpDynamicString dstrTTSVoice; CSpDynamicString dstrGain;
hr = SpGenericSetObjectToken(pToken, m_cpToken);
// Get the associated TTS voice token, set the voice and output site
if ( SUCCEEDED( hr ) ) { hr = m_cpToken->GetStringValue( L"TTSVoice", &dstrName );
if ( SUCCEEDED( hr ) && dstrName.Length() ) { dstrTTSVoice = L"NAME="; dstrTTSVoice.Append(dstrName);
CComPtr<IEnumSpObjectTokens> cpEnum; hr = SpEnumTokens( SPCAT_VOICES, dstrTTSVoice, NULL, &cpEnum ); if( SUCCEEDED(hr) ) { hr = cpEnum->Next( 1, &m_cpTTSToken, NULL ); }
// Any tokens?
if ( hr != S_OK ) { hr = E_INVALIDARG; }
// Make sure the secondary voice is not another Prompt voice!
if ( SUCCEEDED(hr) ) { CSpDynamicString dstrTTSEngId; CSpDynamicString dstrPromptEngId = CLSID_PromptEng; hr = m_cpTTSToken->GetStringValue( L"CLSID", &dstrTTSEngId ); if ( SUCCEEDED(hr) ) { if ( !wcscmp(dstrPromptEngId, dstrTTSEngId) ) { hr = E_INVALIDARG; } } dstrPromptEngId.Clear(); dstrTTSEngId.Clear(); }
if ( SUCCEEDED(hr) ) { hr = SpCreateObjectFromToken( m_cpTTSToken, &m_cpTTSEngine ); } } dstrName.Clear(); dstrTTSVoice.Clear(); } // Allow no TTS Engine
if ( FAILED(hr) ) { //SPDBG_ASSERT(!m_cpTTSEngine);
hr = S_FALSE; }
// Gain factor for Prompt Entry output
if ( SUCCEEDED( hr ) ) { hr = m_cpToken->GetStringValue( L"PromptGain", &dstrGain ); if ( SUCCEEDED(hr) ) { hr = m_cpPromptDb->SetEntryGain( wcstod( dstrGain, NULL ) ); } dstrGain.Clear(); }
// Load the text expansion/normalization rules
// Implementation is script-language independent: language specified in registry entry.
if ( SUCCEEDED( hr ) ) { wcscpy(pszKey, L"PromptRules"); hr = m_cpToken->OpenKey(pszKey, &pDataKey);
if ( SUCCEEDED(hr) ) { pDataKey->GetStringValue( L"Path", &dstrPath ); pDataKey->GetStringValue( L"ScriptLanguage", &dstrName ); if ( dstrName.Length() && dstrPath.Length() ) { hr = m_textRules.ReadRules(dstrName, dstrPath); } dstrPath.Clear(); dstrName.Clear(); pDataKey->Release(); pDataKey = NULL; } else { hr = S_FALSE; // allow no rules file
} }
// Load the phone context file. (Might be able to do this from language id instead of registry entry.)
if ( SUCCEEDED( hr ) ) { hr = m_cpToken->GetStringValue( L"PhContext", &dstrPath ); if ( SUCCEEDED ( hr ) ) { if ( wcscmp( dstrPath, L"DEFAULT" ) == 0 ) { hr = m_phoneContext.LoadDefault(); } else { hr = m_phoneContext.Load(dstrPath); } } else { hr = m_phoneContext.LoadDefault(); } dstrPath.Clear(); }
// Get all of the Db Entries from the registry for this voice, add them to the list
while ( SUCCEEDED ( hr ) ) { CSpDynamicString dstrID;
swprintf( pszKey, L"PromptData%hu", i );
hr = m_cpToken->OpenKey(pszKey, &pDataKey); if ( i > 0 && hr == SPERR_NOT_FOUND ) { hr = S_OK; break; } if ( SUCCEEDED(hr) ) { hr = pDataKey->GetStringValue( L"Path", &dstrPath ); if ( FAILED(hr) || !wcslen(dstrPath) ) { hr = S_FALSE; // empty keys are just skipped.
} } if ( hr == S_OK ) { hr = pDataKey->GetStringValue( L"Name", &dstrName ); if ( FAILED(hr) || !wcslen(dstrName) ) { dstrName = L"DEFAULT"; hr = S_OK; } } if ( hr == S_OK ) { pDataKey->GetStringValue( L"ID Set", &dstrID ); // might not exist
} if ( hr == S_OK ) { WStringUpperCase(dstrName); if ( dstrID ) { WStringUpperCase(dstrID); } hr = m_cpPromptDb->AddDb( dstrName, dstrPath, dstrID, DB_LOAD); } if ( SUCCEEDED ( hr ) ) { i++; } dstrPath.Clear(); dstrName.Clear(); dstrID.Clear(); if ( pDataKey ) { pDataKey->Release(); pDataKey = NULL; } }
// Activate the default Db (or first Db, if "DEFAULT" doesn't exist)
if ( SUCCEEDED(hr) ) { hr = m_cpPromptDb->ActivateDbName(L"DEFAULT");
if ( FAILED (hr) ) { hr = m_cpPromptDb->ActivateDbNumber(0); } } // Initialize the Db Searching class
if ( SUCCEEDED( hr ) ) { hr = m_DbQuery.Init( m_cpPromptDb.p, &m_phoneContext ); }
if ( SUCCEEDED(hr) ) { hr = m_pOutputSite->SetBufferSize(20); }
SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::GetOutputFormat
//
// Gets the output format for the prompts and TTS.
//
//////////////////////////////////////////////////////////// JOEM 02-2000 //
STDMETHODIMP CPromptEng::GetOutputFormat( const GUID * pTargetFormatId, const WAVEFORMATEX * pTargetWaveFormatEx, GUID * pDesiredFormatId, WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx ) { SPDBG_FUNC( "CPromptEng::GetOutputFormat" ); HRESULT hr = S_OK;
SPDBG_ASSERT(m_cpPromptDb);
if( ( SP_IS_BAD_WRITE_PTR(pDesiredFormatId) ) || ( SP_IS_BAD_WRITE_PTR(ppCoMemDesiredWaveFormatEx) ) ) { hr = E_INVALIDARG; }
if ( SUCCEEDED(hr) ) { // Use TTS preferred format if available
if ( m_cpTTSEngine ) { hr = m_cpTTSEngine->GetOutputFormat( pTargetFormatId, pTargetWaveFormatEx, pDesiredFormatId, ppCoMemDesiredWaveFormatEx ); } else if ( m_cpPromptDb ) { // otherwise, agree to text format if requested...
if ( pTargetFormatId && *pTargetFormatId == SPDFID_Text ) { *pDesiredFormatId = SPDFID_Text; *ppCoMemDesiredWaveFormatEx = NULL; } // ...or use prompt format.
else { hr = m_cpPromptDb->GetPromptFormat(ppCoMemDesiredWaveFormatEx); if ( SUCCEEDED(hr) ) { *pDesiredFormatId = SPDFID_WaveFormatEx; } } } }
if ( SUCCEEDED(hr) ) { if ( pDesiredFormatId ) { hr = m_pOutputSite->SetOutputFormat( pDesiredFormatId, *ppCoMemDesiredWaveFormatEx ); if ( SUCCEEDED(hr) ) { hr = m_cpPromptDb->SetOutputFormat( pDesiredFormatId, *ppCoMemDesiredWaveFormatEx ); } } else { hr = m_pOutputSite->SetOutputFormat( pTargetFormatId, pTargetWaveFormatEx ); if ( SUCCEEDED(hr) ) { hr = m_cpPromptDb->SetOutputFormat( pTargetFormatId, pTargetWaveFormatEx ); } } }
SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::Speak
//
// Builds a Db Query list from the frag list, and dispatches the fragments
// to prompts or tts accordingly.
//
//////////////////////////////////////////////////////////// JOEM 02-2000 //
STDMETHODIMP CPromptEng::Speak(DWORD dwSpeakFlags, REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx, const SPVTEXTFRAG* pTextFragList, ISpTTSEngineSite* pOutputSite) { SPDBG_FUNC( "CPromptEng::Speak" ); HRESULT hr = S_OK; m_fAbort = false;
//--- Check args
if( SP_IS_BAD_INTERFACE_PTR( pOutputSite ) || SP_IS_BAD_READ_PTR( pTextFragList ) || ( dwSpeakFlags & SPF_UNUSED_FLAGS ) ) { return E_INVALIDARG; }
if ( m_pOutputSite ) { m_pOutputSite->SetOutputSite( pOutputSite ); } if ( SUCCEEDED (hr) ) { hr = BuildQueryList( dwSpeakFlags, pTextFragList, SAPI_FRAG ); } //DebugQueryList();
if ( SUCCEEDED(hr) && !m_fAbort ) { hr = CompressQueryList(); } //DebugQueryList();
if ( SUCCEEDED(hr) && !m_fAbort ) { hr = m_DbQuery.Query( &m_apQueries, &m_dQueryCost, m_pOutputSite, &m_fAbort ); } //DebugQueryList();
if ( SUCCEEDED(hr) && !m_fAbort ) { hr = CompressTTSItems(rguidFormatId); } //DebugQueryList();
if ( SUCCEEDED (hr) && !m_fAbort ) { hr = DispatchQueryList(dwSpeakFlags, rguidFormatId, pWaveFormatEx); }
// Successful or not, delete the query list
for ( int i=0; i<m_apQueries.GetSize(); i++ ) { if ( m_apQueries[i] ) { delete m_apQueries[i]; m_apQueries[i] = NULL; } } m_apQueries.RemoveAll(); m_dQueryCost = 0.0;
SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::BuildQueryList
//
// Steps through the frag list, handling known XML tags, and builds a list
// of CQuery items. This function is pseudo-recursive: If a text expansion
// rule modifies a text frag, this calls CPromptEng::ParseSubQuery to build a
// secondary fragment list. ParseSubQuery then calls this function again.
//
//////////////////////////////////////////////////////////// JOEM 02-2000 //
STDMETHODIMP CPromptEng::BuildQueryList(const DWORD dwSpeakFlags, const SPVTEXTFRAG* pCurrentFrag, const FragType fFragType) { SPDBG_FUNC( "CPromptEng::BuildQueryList" ); HRESULT hr = S_OK; USHORT i = 0; USHORT subQueries = 0; bool fTTSOnly = false; bool fRuleDone = false; CQuery* pQuery = NULL; WCHAR* pszRuleName = NULL; CSPArray<CDynStr,CDynStr> aTags;
if ( SP_IS_BAD_READ_PTR( pCurrentFrag ) ) { return E_INVALIDARG; }
while ( pCurrentFrag && SUCCEEDED(hr) ) { // Stop speaking?
if ( m_pOutputSite->GetActions() & SPVES_ABORT ) { m_fAbort = true; break; } if ( SUCCEEDED(hr) ) { // If there is nothing in this frag, just go immediately to next one
if ( pCurrentFrag->State.eAction == SPVA_Speak && !pCurrentFrag->ulTextLen ) { // Go to the next frag
pCurrentFrag = pCurrentFrag->pNext; continue; } }
if ( SUCCEEDED(hr) ) { // Otherwise, create a new query
pQuery = new CQuery; if ( !pQuery ) { hr = E_OUTOFMEMORY; } }
if ( SUCCEEDED(hr) ) { pQuery->m_fFragType = fFragType; // Apply a rule to expand the text, if necessary
if ( pszRuleName && !fRuleDone) { fRuleDone = true; WCHAR* pszOriginalText = (WCHAR*) malloc( sizeof(WCHAR) * (pCurrentFrag->ulTextLen + 1) ); if ( !pszOriginalText ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { wcsncpy(pszOriginalText, pCurrentFrag->pTextStart, pCurrentFrag->ulTextLen); pszOriginalText[pCurrentFrag->ulTextLen] = L'\0'; hr = m_textRules.ApplyRule( pszRuleName, pszOriginalText, &pQuery->m_pszExpandedText ); if ( SUCCEEDED(hr) ) { free(pszOriginalText); pszOriginalText = NULL; USHORT currListSize = (USHORT) m_apQueries.GetSize(); hr = ParseSubQuery( dwSpeakFlags, pQuery->m_pszExpandedText, &subQueries ); // Adjust the text offset and len here. ParseSubQuery needs the len of the modified
// text, but later, the app will need the offset & len of the original text for highlighting it.
for ( i=currListSize; i<m_apQueries.GetSize(); i++ ) { m_apQueries[i]->m_ulTextOffset = pCurrentFrag->ulTextSrcOffset; m_apQueries[i]->m_ulTextLen = pCurrentFrag->ulTextLen; } // Prefer to speak the subqueries rather than the orig frag.
// But keep the orig, in case subq's fail.
if ( subQueries ) { pQuery->m_fSpeak = false; } // if ( !subQueries )
} else // If the rule application fails, just use the original text.
{ hr = S_OK; //SPDBG_ASSERT( ! "Rule doesn't exist." );
pQuery->m_pszExpandedText = pszOriginalText; } } } else // if no expansion rule, set the expanded text to the original text
{ pQuery->m_pszExpandedText = (WCHAR*) malloc( sizeof(WCHAR) * (pCurrentFrag->ulTextLen + 1)); if ( !pQuery->m_pszExpandedText ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { wcsncpy(pQuery->m_pszExpandedText, pCurrentFrag->pTextStart, pCurrentFrag->ulTextLen); pQuery->m_pszExpandedText[pCurrentFrag->ulTextLen] = L'\0'; } } // else
} // if ( SUCCEEDED(hr) )
// Unicode control chars map to space
if ( SUCCEEDED(hr) && pQuery->m_pszExpandedText ) { WCHAR* psz = pQuery->m_pszExpandedText; while ( ( psz = FindUnicodeControlChar(psz) ) != NULL ) { psz[0] = L' '; } } // HANDLE XML IN THE TEXT FRAG
if ( SUCCEEDED(hr) ) { // Is there an unknown tag is this fragment?
if ( pCurrentFrag->State.eAction == SPVA_ParseUnknownTag ) { CXMLTag unknownTag; hr = unknownTag.ParseUnknownTag( pQuery->m_pszExpandedText ); // if this is a new RULE, set flag to process it.
if ( SUCCEEDED(hr) ) { const WCHAR* pszName = NULL; hr = unknownTag.GetTagName(&pszName); if ( wcscmp(pszName, XMLTags[RULE_END].pszTag) == 0 ) { fRuleDone = false; } if ( SUCCEEDED (hr) ) { hr = unknownTag.ApplyXML( pQuery, fTTSOnly, pszRuleName, aTags ); if ( hr == S_FALSE ) { // Don't know this XML -- must ignore or send it to TTS
if ( !fTTSOnly ) { pQuery->m_afEntryMatch.Add(false); } else { pQuery->m_afEntryMatch.Add(true); } } } else { pQuery->m_fSpeak = false; hr = S_OK; } } } // if ( pCurrentFrag->State.eAction == SPVA_ParseUnknownTag )
else if ( pCurrentFrag->State.eAction == SPVA_Bookmark ) { pQuery->m_fXML = UNKNOWN_XML; if ( !fTTSOnly ) { pQuery->m_afEntryMatch.Add(false); } else { pQuery->m_afEntryMatch.Add(true); } } else if ( pCurrentFrag->State.eAction == SPVA_Silence ) { pQuery->m_fXML = SILENCE; if ( !fTTSOnly ) { pQuery->m_afEntryMatch.Add(false); } else { pQuery->m_afEntryMatch.Add(true); } } } // if ( SUCCEEDED(hr) )
// Finish setting up this query item
// We need a copy of the text frag, so we can break the links
// when inserting new frags based on text expansion rules.
if ( SUCCEEDED(hr) ) { pQuery->m_pTextFrag = new SPVTEXTFRAG(*pCurrentFrag); if ( !pQuery->m_pTextFrag ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { pQuery->m_pTextFrag->pNext = NULL; pQuery->m_ulTextOffset = pCurrentFrag->ulTextSrcOffset; pQuery->m_ulTextLen = pCurrentFrag->ulTextLen; // Use TTS if:
// - it's a TTS item
// - text is too long
// - need to speak punctuation
if ( (dwSpeakFlags & SPF_NLP_SPEAK_PUNC) || fTTSOnly || pQuery->m_pTextFrag->ulTextLen > MAX_SEARCH_LEN ) { pQuery->m_fTTS = true; } // This keeps track of WITHTAG tags
if ( aTags.GetSize() ) { pQuery->m_paTagList = new CSPArray<CDynStr,CDynStr>; if ( !pQuery->m_paTagList ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { for ( i=0; i < aTags.GetSize(); i++) { pQuery->m_paTagList->Add(aTags[i]); } } } // If this is a TTS item, make sure there is a TTS engine
if ( pQuery->m_fTTS && !m_cpTTSEngine ) { hr = PEERR_NO_TTS_VOICE; }
// Add it to the query list
m_apQueries.Add(pQuery); pQuery = NULL; subQueries = 0;
// Go to the next frag
pCurrentFrag = pCurrentFrag->pNext; } } } // while ( pCurrentFrag )
// MEMORY CLEANUP ON ERROR
if ( FAILED(hr) ) { if ( pszRuleName ) { free(pszRuleName); pszRuleName = NULL; } if ( pQuery ) { delete pQuery; pQuery = NULL; }
for ( i=0; i<aTags.GetSize(); i++ ) { aTags[i].dstr.Clear(); } aTags.RemoveAll();
for ( i=0; i<m_apQueries.GetSize(); i++ ) { if ( m_apQueries[i] ) { delete m_apQueries[i]; m_apQueries[i] = NULL; } } m_apQueries.RemoveAll(); }
// This makes sure we don't leak memory if user forgets to close <RULE> </RULE> tag.
if ( pszRuleName ) { free(pszRuleName); pszRuleName = NULL; }
SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::ParseSubQuery
//
// When a text expansion rule modifies a text fragment, it may insert new
// XML tags that need to be added to the fragment list. This function is
// called to build a new fragment list out of the modified text (which may
// or may not include new XML tags). This function then calls
// CPromptEng::BuildQueryList to add CQueries based on the new fragment list.
//
//////////////////////////////////////////////////////////// JOEM 04-2000 //
STDMETHODIMP CPromptEng::ParseSubQuery(const DWORD dwSpeakFlags, const WCHAR *pszText, USHORT* unSubQueries) { SPDBG_FUNC( "CPromptEng::ParseSubQuery" ); HRESULT hr = S_OK; const WCHAR* p = NULL; const WCHAR* pTagStart = NULL; const WCHAR* pTagEnd = NULL; const WCHAR* pStart = NULL; SPVTEXTFRAG* pNextFrag = NULL; SPVTEXTFRAG* pFrag = NULL; SPVTEXTFRAG* pFirstFrag = NULL;
SPDBG_ASSERT(pszText); // need a copy of this text
pStart = pszText; p = pszText;
WSkipWhiteSpace(pStart); WSkipWhiteSpace(pStart);
// Find an XML tag
while ( pTagStart = wcschr(p, L'<') ) { // if the first char is a tag, make a frag out of it
if ( pTagStart == p ) { pTagEnd = wcschr(pTagStart, L'>'); if ( !pTagEnd ) { hr = E_INVALIDARG; }
// Create a new SPVTEXTFRAG consisting of just this XML tag.
if ( SUCCEEDED(hr) ) { pNextFrag = new SPVTEXTFRAG; if ( !pNextFrag ) { hr = E_OUTOFMEMORY; } } if ( SUCCEEDED(hr) ) { memset( pNextFrag, 0, sizeof (SPVTEXTFRAG) ); memset( &pNextFrag->State, 0, sizeof (SPVSTATE) ); pNextFrag->pTextStart = pTagStart; pNextFrag->ulTextLen = pTagEnd - pTagStart + 1; pNextFrag->State.eAction = SPVA_ParseUnknownTag; p = ++pTagEnd; (*unSubQueries)++; } } // if some text was skipped when searching for '<',
// make a frag out of the skipped text.
else { pNextFrag = new SPVTEXTFRAG; if ( !pNextFrag ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { memset( pNextFrag, 0, sizeof(SPVTEXTFRAG) ); memset( &pNextFrag->State, 0, sizeof (SPVSTATE) ); pNextFrag->pTextStart = p; pNextFrag->ulTextLen = pTagStart - p; pNextFrag->State.eAction = SPVA_Speak; p = pTagStart; (*unSubQueries)++; } }
if ( SUCCEEDED(hr) ) { // Was this the first fragment?
if ( pFrag ) { pFrag->pNext = pNextFrag; } else { pFrag = pNextFrag; pFirstFrag = pFrag; } // Move on to next one
pFrag = pNextFrag; pTagStart = NULL; pTagEnd = NULL; pNextFrag = NULL; WSkipWhiteSpace(p); } }
// If NO tags were found, don't need to create any fragments.
if ( pFirstFrag && SUCCEEDED (hr) ) { // When done finding tags, check for more text and make a fragment out of it.
if ( p < pStart + wcslen(pStart) ) { pNextFrag = new SPVTEXTFRAG; if ( !pNextFrag ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { memset( pNextFrag, 0, sizeof(SPVTEXTFRAG) ); memset( &pNextFrag->State, 0, sizeof (SPVSTATE) ); pNextFrag->pTextStart = p; pNextFrag->ulTextLen = pStart + wcslen(pStart) - p; pNextFrag->State.eAction = SPVA_Speak; pFrag->pNext = pNextFrag; (*unSubQueries)++; } }
hr = BuildQueryList( dwSpeakFlags, pFirstFrag, LOCAL_FRAG ); }
pFrag = pFirstFrag; while ( pFrag ) { pNextFrag = pFrag->pNext; delete pFrag; pFrag = pNextFrag; }
SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::CompressQueryList
//
// Adjacent Db Search items are combined (before search).
//
// Note that there may be intervening XML between search items.
// If intervening XML is unknown:
// - ignore it and go to the next search item,
// - but save it in case the search fails (so it can be restored and
// passed on to TTS).
// If intervening XML is KNOWN, stop combining, so XML can be processed
// during the search.
//
//////////////////////////////////////////////////////////// JOEM 06-2000 //
STDMETHODIMP CPromptEng::CompressQueryList() { SPDBG_FUNC( "CPromptEng::CompressQueryList" ); HRESULT hr = S_OK; CQuery* pCurrentQuery = NULL; CQuery* pPreviousQuery = NULL; USHORT unPrevSize = 0; USHORT unCurrSize = 0; USHORT i = 0; int j = 0;
for ( i = 0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ ) { pPreviousQuery = pCurrentQuery;
while ( SUCCEEDED(hr) && i < m_apQueries.GetSize() ) { if ( SUCCEEDED(hr) ) { pCurrentQuery = m_apQueries[i];
SPDBG_ASSERT(pCurrentQuery); // Don't combine if either query:
// - is not marked "Speak" or
// - is marked TTS or
// - is an ID or
// - is just an SPVSTATE (no m_pszExpandedText) or
// - is known XML or
// - is silence
// - has tags or
// - is a local frag or
// - (special case) there is a volume change
// - (special case) there is a rate change
if (!pPreviousQuery || !pPreviousQuery->m_fSpeak || pPreviousQuery->m_fTTS || pPreviousQuery->m_pszId || !pPreviousQuery->m_pszExpandedText || pPreviousQuery->m_fXML == KNOWN_XML || pPreviousQuery->m_fXML == SILENCE || pPreviousQuery->m_paTagList || pPreviousQuery->m_fFragType == LOCAL_FRAG ||
!pCurrentQuery->m_fSpeak || pCurrentQuery->m_fTTS || pCurrentQuery->m_pszId || !pCurrentQuery->m_pszExpandedText || pCurrentQuery->m_fXML == KNOWN_XML || pCurrentQuery->m_fXML == SILENCE || pCurrentQuery->m_paTagList || pCurrentQuery->m_fFragType == LOCAL_FRAG || pPreviousQuery->m_pTextFrag->State.Volume != pCurrentQuery->m_pTextFrag->State.Volume || pPreviousQuery->m_pTextFrag->State.RateAdj != pCurrentQuery->m_pTextFrag->State.RateAdj || // And, don't combine it with any previous unknown XML.
( pPreviousQuery->m_fFragType != COMBINED_FRAG && pPreviousQuery->m_fXML == UNKNOWN_XML ) ) { // Don't combine this item? Then restore all immediately preceding unknown XML tags.
for ( j=i-1; j>=0; j-- ) { if ( m_apQueries[j]->m_fXML == UNKNOWN_XML ) { m_apQueries[j]->m_fFragType = SAPI_FRAG; m_apQueries[j]->m_fSpeak = true; } else { break; } } break; }
// Combine search items.
pPreviousQuery->m_fFragType = COMBINED_FRAG; // adjust the length - this is complicated because unknown tags in text aren't processed here
ULONG OffsetFromFirstPrompt = pCurrentQuery->m_ulTextOffset - pPreviousQuery->m_ulTextOffset; pPreviousQuery->m_ulTextLen = OffsetFromFirstPrompt + pCurrentQuery->m_ulTextLen;
// Unknown XML? don't really combine - just mark it out and skip to next one
if ( pCurrentQuery->m_fXML == UNKNOWN_XML ) { pCurrentQuery->m_fFragType = COMBINED_FRAG; pCurrentQuery->m_fSpeak = false; i++; continue; }
// Append current text to previous
unPrevSize = sizeof(pPreviousQuery->m_pszExpandedText) * wcslen(pPreviousQuery->m_pszExpandedText); unCurrSize = sizeof(pCurrentQuery->m_pszExpandedText) * wcslen(pCurrentQuery->m_pszExpandedText); pPreviousQuery->m_pszExpandedText = (WCHAR*) realloc( pPreviousQuery->m_pszExpandedText, unPrevSize + unCurrSize + sizeof(WCHAR) ); if ( !pPreviousQuery->m_pszExpandedText ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { wcscat(pPreviousQuery->m_pszExpandedText, L" "); wcscat(pPreviousQuery->m_pszExpandedText, pCurrentQuery->m_pszExpandedText); } if ( SUCCEEDED(hr) ) { pCurrentQuery->m_fFragType = COMBINED_FRAG; pCurrentQuery->m_fSpeak = false; i++; } } // if ( SUCCEEDED(hr) )
} // while
} // for
SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::CompressTTSItems
//
// Adjacent TTS items are combined (after search).
//
//////////////////////////////////////////////////////////// JOEM 06-2000 //
STDMETHODIMP CPromptEng::CompressTTSItems(REFGUID rguidFormatId) { SPDBG_FUNC( "CPromptEng::CompressTTSItems" ); HRESULT hr = S_OK; CQuery* pCurrentQuery = NULL; CQuery* pPreviousQuery = NULL; SPVTEXTFRAG* pFrag = NULL; USHORT i = 0;
for ( i = 0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ ) { while ( SUCCEEDED(hr) && i < m_apQueries.GetSize() ) { pCurrentQuery = m_apQueries[i]; SPDBG_ASSERT(pCurrentQuery);
// If this is a TTS item, make sure there is a TTS engine
if ( pCurrentQuery->m_fTTS && !m_cpTTSEngine ) { // otherwise, if it's just unknown xml, ignore it
if ( pCurrentQuery->m_fXML == UNKNOWN_XML ) { pCurrentQuery->m_fSpeak = false; } else { hr = PEERR_NO_TTS_VOICE; } }
if ( SUCCEEDED(hr) ) { // Don't combine if either query:
// - if the output format is text
// - is marked "don't speak"
// - is not TTS
if ( rguidFormatId == SPDFID_Text || !pPreviousQuery || !pPreviousQuery->m_fSpeak || !pPreviousQuery->m_fTTS || !pCurrentQuery->m_fSpeak || !pCurrentQuery->m_fTTS ) { if ( pCurrentQuery->m_fSpeak ) { pPreviousQuery = pCurrentQuery; } break; } pFrag = pPreviousQuery->m_pTextFrag; while ( pFrag->pNext ) { pFrag = pFrag->pNext; } pFrag->pNext = pCurrentQuery->m_pTextFrag; m_apQueries[i]->m_fSpeak = false; i++; } } // while
} // for
SPDBG_REPORT_ON_FAIL( hr ); return hr;
}
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::DispatchQueryList
//
// Goes through the query list, sending TTS or prompts as appropriate.
//
//////////////////////////////////////////////////////////// JOEM 02-2000 //
STDMETHODIMP CPromptEng::DispatchQueryList(const DWORD dwSpeakFlags, REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx) { SPDBG_FUNC( "CPromptEng::DispatchQueryList" ); HRESULT hr = S_OK; USHORT i = 0; SHORT j = 0; CQuery* pQuery = NULL; CPromptEntry* entry = NULL;
if ( rguidFormatId == SPDFID_Text ) { hr = SendTextOutput( dwSpeakFlags, rguidFormatId ); } else { for ( i=0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ ) { // Stop speaking?
if ( m_pOutputSite->GetActions() & SPVES_ABORT ) { m_fAbort = true; break; } pQuery = m_apQueries[i]; if ( !pQuery->m_fSpeak ) { continue; } if ( pQuery->m_fTTS ) { // Should not get to this point without a TTS Engine!
SPDBG_ASSERT(m_cpTTSEngine); // TTS items get passed directly to TTS Engine
if ( SUCCEEDED(hr) ) { hr = m_cpTTSEngine->Speak(dwSpeakFlags, rguidFormatId, NULL, pQuery->m_pTextFrag, m_pOutputSite); if ( SUCCEEDED(hr) ) { m_pOutputSite->UpdateBytesWritten(); } } } else { if ( SUCCEEDED(hr) && ( pQuery->m_fFragType != LOCAL_FRAG ) ) { m_cpPromptDb->SetXMLVolume(pQuery->m_pTextFrag->State.Volume); m_cpPromptDb->SetXMLRate(pQuery->m_pTextFrag->State.RateAdj); } if ( pQuery->m_fXML == SILENCE ) { hr = SendSilence(pQuery->m_pTextFrag->State.SilenceMSecs, pWaveFormatEx->nAvgBytesPerSec); } else { // The search process backtracks the list, so these are in reverse order.
// So, play in last-to-first order.
for ( j=pQuery->m_apEntry.GetSize()-1; j>=0; j-- ) { entry = pQuery->m_apEntry[j]; if ( !entry ) { hr = E_FAIL; } if ( SUCCEEDED(hr) ) { hr = m_cpPromptDb->SendEntrySamples(entry, m_pOutputSite, pQuery->m_ulTextOffset, pQuery->m_ulTextLen); } } // for ( j=pQuery->m_apEntry.GetSize()-1 ...
} } } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } /////////////////////////////////////////////////////////////////////////////
// CPromptEng::SendSilence
//
// Generates silence and writes to output site.
//
//////////////////////////////////////////////////////////// JOEM 11-2000 //
STDMETHODIMP CPromptEng::SendSilence(const int iMsec, const DWORD iAvgBytesPerSec) { SPDBG_FUNC( "CPromptEng::SendSilence" ); HRESULT hr = S_OK;
BYTE* pbSilence = NULL; int iBytes = iMsec/1000 * iAvgBytesPerSec;
if ( iBytes ) { pbSilence = new BYTE[iBytes]; if ( !pbSilence ) { hr = E_OUTOFMEMORY; }
if ( SUCCEEDED(hr) ) { memset( (void*) pbSilence, 0, iBytes * sizeof(BYTE) ); hr = m_pOutputSite->Write(pbSilence, iBytes, 0);
if ( SUCCEEDED(hr) ) { m_pOutputSite->UpdateBytesWritten(); } delete [] pbSilence; pbSilence = NULL; } }
SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::SendTextOutput
//
// Output function for SPDFID_Text format. Goes through the query list,
// sending text from TTS or prompts as appropriate.
//
// This functionality is needed mainly for the test team.
//
//////////////////////////////////////////////////////////// JOEM 11-2000 //
STDMETHODIMP CPromptEng::SendTextOutput(const DWORD dwSpeakFlags, REFGUID rguidFormatId) { SPDBG_FUNC( "CPromptEng::SendTextOutput" ); HRESULT hr = S_OK; USHORT i = 0; SHORT j = 0; CQuery* pQuery = NULL; static const WCHAR Signature = 0xFEFF; // write the Unicode signature
hr = m_pOutputSite->Write( &Signature, sizeof(Signature), NULL );
for ( i=0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ ) { // Stop speaking?
if ( m_pOutputSite->GetActions() & SPVES_ABORT ) { m_fAbort = true; break; } pQuery = m_apQueries[i]; if ( !pQuery->m_fSpeak ) { continue; } if ( pQuery->m_fTTS ) { // Should not get to this point without a TTS Engine!
SPDBG_ASSERT(m_cpTTSEngine); WCHAR szText[7] = L"0:"; // 7 chars for "0:TTS:" (6 plus \0)
if ( pQuery->m_afEntryMatch.GetSize() && pQuery->m_afEntryMatch[0] ) { wcscpy(szText, L"1:"); } wcscat(szText, L"TTS:"); hr = m_pOutputSite->Write( szText, (wcslen(szText) + 1) * sizeof(WCHAR), NULL ); // TTS items get passed directly to TTS Engine
if ( SUCCEEDED(hr) ) { hr = m_cpTTSEngine->Speak(dwSpeakFlags, rguidFormatId, NULL, pQuery->m_pTextFrag, m_pOutputSite); if ( SUCCEEDED(hr) ) { m_pOutputSite->UpdateBytesWritten(); } } } else { CPromptEntry* entry = NULL;
if ( SUCCEEDED(hr) && ( pQuery->m_fFragType != LOCAL_FRAG ) ) { hr = m_cpPromptDb->SetXMLVolume(pQuery->m_pTextFrag->State.Volume); } // The search process backtracks the list, so these are in reverse order.
// So, play in last-to-first order.
for ( j=pQuery->m_apEntry.GetSize()-1; j>=0; j-- ) { entry = pQuery->m_apEntry[j]; if ( !entry ) { hr = E_FAIL; } if ( SUCCEEDED(hr) ) { // For text output (TEST HOOKS)
WCHAR* pszOutput = NULL; WCHAR* pszTagOutput = NULL; WCHAR szMatch = L'0'; const WCHAR* pszId = NULL; const WCHAR* pszText = NULL; const WCHAR* pszTag = NULL; int iLen = 0; USHORT k = 0; USHORT unTags = 0; // get the tag match indicator
SPDBG_ASSERT(j < pQuery->m_afEntryMatch.GetSize()); if ( pQuery->m_afEntryMatch[j] ) { szMatch = L'1'; } // get the ID#, text, and tags -- and write them in the output
if ( SUCCEEDED(hr) ) { // Get the ID
if ( SUCCEEDED(hr) ) { hr = entry->GetId(&pszId); } // Get the Text
if ( SUCCEEDED(hr) ) { hr = entry->GetText(&pszText); } // Assemble the collection of Tags
hr = entry->CountTags(&unTags); if ( SUCCEEDED(hr) ) { for ( k=0; SUCCEEDED(hr) && k<unTags; k++ ) { hr = entry->GetTag(&pszTag, k); if ( SUCCEEDED(hr) ) { iLen += wcslen(pszTag); iLen++; // +1 for the comma or \0
} } if ( iLen ) { pszTagOutput = new WCHAR[iLen]; } else { pszTagOutput = new WCHAR[2]; } if ( !pszTagOutput ) { hr = E_OUTOFMEMORY; } else { if ( !iLen ) { wcscpy( pszTagOutput, L" " ); } } for ( k=0; SUCCEEDED(hr) && k<unTags; k++ ) { hr = entry->GetTag(&pszTag, k); if ( SUCCEEDED(hr) ) { if ( k==0 ) { wcscpy(pszTagOutput, pszTag); } else { wcscat(pszTagOutput, L","); wcscat(pszTagOutput, pszTag); } } } } // Put it all together
if ( SUCCEEDED(hr) ) { if ( pszText && pszId ) { iLen = wcslen(pszId) + wcslen(pszTagOutput) + wcslen(pszText) + wcslen(L"0:ID()():\r\n"); pszOutput = new WCHAR[iLen+1]; if ( !pszOutput ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { // signal to the app that the upcoming output is from prompts, not TTS.
swprintf (pszOutput, L"%c:ID(%s)(%s):%s\r\n", szMatch, pszId, pszTagOutput, pszText); hr = m_pOutputSite->Write(pszOutput, (wcslen(pszOutput) + 1) * sizeof(WCHAR), NULL); } } else // no text/id, must be an XML-specified wav file
{ const WCHAR* pszPath = NULL; hr = entry->GetFileName(&pszPath);
if ( SUCCEEDED(hr) ) { // make sure that the file is actually wav data
VapiIO* pVapiIO = VapiIO::ClassFactory(); if ( !pVapiIO ) { hr = E_OUTOFMEMORY; }
if ( SUCCEEDED(hr) ) { if ( pVapiIO->OpenFile(pszPath, VAPI_IO_READ) != 0 ) { hr = E_ACCESSDENIED; } delete pVapiIO; pVapiIO = NULL; } }
if ( SUCCEEDED(hr) ) { iLen = wcslen(L"1:WAV()\r\n") + wcslen(pszPath) + 1; pszOutput = new WCHAR[iLen]; swprintf (pszOutput, L"1:WAV(%s)\r\n", pszPath); hr = m_pOutputSite->Write(pszOutput, (wcslen(pszOutput) + 1) * sizeof(WCHAR), NULL); } } } if ( pszOutput ) { delete [] pszOutput; pszOutput = NULL; } if ( pszTagOutput ) { delete [] pszTagOutput; pszTagOutput = NULL; } } } } // for ( j=pQuery->m_apEntry.GetSize()-1 ...
} } // TEST HOOK: OUTPUT THE PATH COST
if ( SUCCEEDED(hr) && !m_fAbort ) { const iStrLen = 20; // arbitrary -- plenty of space for 6 dig precision, plus sign, exponent, etc if necessary
char szCost[iStrLen]; WCHAR wszCost[iStrLen]; WCHAR* pszOutput = NULL; pszOutput = new WCHAR[iStrLen + wcslen(L"Cost: \r\n")]; if ( !pszOutput ) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { _gcvt( m_dQueryCost, 6, szCost ); // convert to string
if ( !MultiByteToWideChar( CP_ACP, 0, szCost, -1, wszCost, iStrLen ) ) { hr = E_FAIL; } if ( SUCCEEDED(hr) ) { swprintf (pszOutput, L"Cost: %.6s\r\n", wszCost ); hr = m_pOutputSite->Write(pszOutput, (wcslen(pszOutput) + 1) * sizeof(WCHAR), NULL); } } if ( pszOutput ) { delete [] pszOutput; pszOutput = NULL; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; }
/////////////////////////////////////////////////////////////////////////////
// CPromptEng::DebugQueryList
//
// Just outputs the contents of each CQuery in the query list.
//
//////////////////////////////////////////////////////////// JOEM 04-2000 //
void CPromptEng::DebugQueryList() { SPDBG_FUNC( "CPromptEng::DebugQueryList" ); USHORT i = 0; USHORT j = 0; USHORT k = 0; CQuery* query = NULL; CPromptEntry* entry = NULL; double dEntryInfo = 0.0; const WCHAR* szEntryInfo = NULL; USHORT count = 0; const USHORT MAX_FRAG = 1024; WCHAR DebugStr[MAX_FRAG+1];
if ( !m_apQueries.GetSize() ) { OutputDebugStringW(L"\nNO QUERY LIST!\n\n"); return; }
for (i=0; i<m_apQueries.GetSize(); i++) { query = m_apQueries[i]; swprintf (DebugStr, L"Query %d:\n", i+1); OutputDebugStringW(DebugStr);
if ( !query ) { OutputDebugStringW(L"NULL QUERY!\n"); continue; }
if ( query->m_fSpeak ) { OutputDebugStringW(L"\tSpeak Flag = true\n"); } else { OutputDebugStringW(L"\tSpeak Flag = false\n"); } if ( query->m_fTTS ) { OutputDebugStringW(L"\tTTS Flag = true\n"); } else { OutputDebugStringW(L"\tTTS Flag = false\n"); } if ( query->m_fXML == KNOWN_XML ) { OutputDebugStringW(L"\tKNOWN XML\n"); } else if ( query->m_fXML == UNKNOWN_XML ) { OutputDebugStringW(L"\tUNKNOWN XML\n"); } if ( query->m_fFragType == LOCAL_FRAG ) { OutputDebugStringW(L"\tLOCAL FRAG\n"); } if ( query->m_fFragType == COMBINED_FRAG ) { OutputDebugStringW(L"\tCOMBINED FRAG\n"); }
OutputDebugStringW(L"\tText Frag: "); if ( query->m_pTextFrag && query->m_pTextFrag->pTextStart ) { wcsncpy(DebugStr, query->m_pTextFrag->pTextStart, 1024); DebugStr[1024] = L'\0'; OutputDebugStringW(DebugStr); OutputDebugStringW(L"\n"); }
OutputDebugStringW(L"\tExpanded Text: "); if ( query->m_pszExpandedText ) { OutputDebugStringW(query->m_pszExpandedText); OutputDebugStringW(L"\n"); }
swprintf (DebugStr, L"\tDbAction: %d\n", query->m_unDbAction); OutputDebugStringW(DebugStr); swprintf (DebugStr, L"\tDbIndex: %d\n", query->m_unDbIndex); OutputDebugStringW(DebugStr); if ( query->m_pszDbName ) { OutputDebugStringW(L"\tDbName: "); OutputDebugStringW(query->m_pszDbName); OutputDebugStringW(L"\n"); } if ( query->m_pszDbPath ) { OutputDebugStringW(L"\tDbPath: "); OutputDebugStringW(query->m_pszDbPath); OutputDebugStringW(L"\n"); }
OutputDebugStringW(L"\tID: "); if ( query->m_pszId ) { swprintf (DebugStr, L"%s\n", query->m_pszId); OutputDebugStringW(DebugStr); } else { OutputDebugStringW(L"(none)\n"); } if ( query->m_paTagList ) { OutputDebugStringW(L"\tWITHTAG List:\n"); for (j=0; j<query->m_paTagList->GetSize(); j++) { swprintf (DebugStr, L"\t\t%s\n", (*query->m_paTagList)[j]); OutputDebugStringW(DebugStr); } }
swprintf (DebugStr, L"\tText Offset: %d\n", query->m_ulTextOffset); OutputDebugStringW(DebugStr); swprintf (DebugStr, L"\tText Length: %d\n", query->m_ulTextLen); OutputDebugStringW(DebugStr);
if ( query->m_apEntry.GetSize() ) { for (j=0; j<query->m_apEntry.GetSize(); j++) { entry = query->m_apEntry[j]; if ( entry ) { swprintf (DebugStr, L"\tDbEntry %d:\n", j+1); OutputDebugStringW(DebugStr); entry->GetStart(&dEntryInfo); swprintf (DebugStr, L"\t\tFrom: %f\n", dEntryInfo); OutputDebugStringW(DebugStr); entry->GetEnd(&dEntryInfo); swprintf (DebugStr, L"\t\tTo: %f\n", dEntryInfo); OutputDebugStringW(DebugStr); entry->GetFileName(&szEntryInfo); OutputDebugStringW(L"\t\tFileName: "); OutputDebugStringW(szEntryInfo); OutputDebugStringW(L"\n"); szEntryInfo = NULL; entry->GetText(&szEntryInfo); OutputDebugStringW(L"\t\tText: "); OutputDebugStringW(szEntryInfo); OutputDebugStringW(L"\n"); szEntryInfo = NULL; entry->GetId(&szEntryInfo); OutputDebugStringW(L"\t\tID: "); OutputDebugStringW(szEntryInfo); OutputDebugStringW(L"\n"); szEntryInfo = NULL; entry->GetStartPhone(&szEntryInfo); if ( szEntryInfo ) { OutputDebugStringW(L"\t\tStartPhone: "); OutputDebugStringW(szEntryInfo); OutputDebugStringW(L"\n"); szEntryInfo = NULL; } entry->GetEndPhone(&szEntryInfo); if ( szEntryInfo ) { OutputDebugStringW(L"\t\tEndPhone: "); OutputDebugStringW(szEntryInfo); OutputDebugStringW(L"\n"); szEntryInfo = NULL; } entry->GetLeftContext(&szEntryInfo); if ( szEntryInfo ) { OutputDebugStringW(L"\t\tLeftContext: "); OutputDebugStringW(szEntryInfo); OutputDebugStringW(L"\n"); szEntryInfo = NULL; } entry->GetRightContext(&szEntryInfo); if ( szEntryInfo ) { OutputDebugStringW(L"\t\tRightContext: "); OutputDebugStringW(szEntryInfo); OutputDebugStringW(L"\n"); szEntryInfo = NULL; } OutputDebugStringW(L"\t\tEntry Tags:\n"); entry->CountTags(&count); for (k=0; k<count; k++) { entry->GetTag(&szEntryInfo, k); swprintf (DebugStr, L"\t\t\t%s\n", szEntryInfo); OutputDebugStringW(DebugStr); szEntryInfo = NULL; }
if ( query->m_afEntryMatch[j] ) { OutputDebugStringW(L"\t\tMatch?: true\n"); } else { OutputDebugStringW(L"\t\tMatch: false\n"); }
} // if ( entry )
else { OutputDebugStringW(L"NULL ENTRY.\n"); } } // for
} // if
else { OutputDebugStringW(L"\tDbEntries: none\n"); if ( query->m_afEntryMatch.GetSize() ) { if ( query->m_afEntryMatch[0] ) { OutputDebugStringW(L"\tMatch: true\n"); } else { OutputDebugStringW(L"\tMatch: false\n"); } } else { OutputDebugStringW(L"\tMatch: unknown\n"); } } OutputDebugStringW(L"END OF QUERY.\n\n"); }
swprintf ( DebugStr, L"END OF QUERY LIST. (Total Queries: %d)\n\n", m_apQueries.GetSize() ); OutputDebugStringW(DebugStr); }
|