Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1784 lines
59 KiB

  1. /////////////////////////////////////////////////////////////////////////////
  2. // PromptEng.cpp : Implementation of CPromptEng
  3. //
  4. // Prompt Engine concatenates pre-recorded phrases for voice output, and
  5. // falls back on TTS Engine when pre-recorded phrases are unavailable.
  6. //
  7. // Created by JOEM 01-2000
  8. // Copyright (C) 2000 Microsoft Corporation
  9. // All Rights Reserved
  10. //
  11. //////////////////////////////////////////////////////////// JOEM 01-2000 //
  12. #include "stdafx.h"
  13. #include "MSPromptEng.h"
  14. #include "PromptEng.h"
  15. #include "XMLTag.h"
  16. #include "vapiIO.h"
  17. #include "common.h"
  18. // Text fragments with over 1024 chars should use TTS (no search).
  19. #define MAX_SEARCH_LEN 1024
  20. /////////////////////////////////////////////////////////////////////////////
  21. // CPromptEng::FinalConstruct
  22. //
  23. // Constructor: Creates the Prompt Db object, TTS voice, and local output site
  24. //
  25. //////////////////////////////////////////////////////////// JOEM 01-2000 //
  26. HRESULT CPromptEng::FinalConstruct()
  27. {
  28. SPDBG_FUNC( "CPromptEng::FinalConstruct" );
  29. HRESULT hr = S_OK;
  30. m_pOutputSite = NULL;
  31. m_dQueryCost = 0.0;
  32. m_fAbort = false;
  33. if( SUCCEEDED( hr ) )
  34. {
  35. hr = m_cpPromptDb.CoCreateInstance(CLSID_PromptDb);
  36. }
  37. if ( SUCCEEDED( hr ) )
  38. {
  39. m_pOutputSite = new CLocalTTSEngineSite;
  40. if ( !m_pOutputSite )
  41. {
  42. hr = E_OUTOFMEMORY;
  43. }
  44. }
  45. if ( FAILED( hr ) )
  46. {
  47. if ( m_cpPromptDb )
  48. {
  49. m_cpPromptDb.Release();
  50. }
  51. if ( m_pOutputSite )
  52. {
  53. m_pOutputSite->Release();
  54. }
  55. }
  56. SPDBG_REPORT_ON_FAIL( hr );
  57. return hr;
  58. } /* CPromptEng::FinalConstruct */
  59. /////////////////////////////////////////////////////////////////////////////
  60. // CPromptEng::FinalRelease
  61. //
  62. // Destructor
  63. //
  64. //////////////////////////////////////////////////////////// JOEM 01-2000 //
  65. void CPromptEng::FinalRelease()
  66. {
  67. SPDBG_FUNC( "CPromptEng::FinalRelease" );
  68. USHORT i = 0;
  69. if ( m_cpPromptDb )
  70. {
  71. m_cpPromptDb.Release();
  72. }
  73. if ( m_cpTTSEngine )
  74. {
  75. m_cpTTSEngine.Release();
  76. }
  77. if ( m_pOutputSite )
  78. {
  79. m_pOutputSite->Release();
  80. }
  81. for ( i=0; i<m_apQueries.GetSize(); i++ )
  82. {
  83. if ( m_apQueries[i] )
  84. {
  85. delete m_apQueries[i];
  86. m_apQueries[i] = NULL;
  87. }
  88. }
  89. m_apQueries.RemoveAll();
  90. } /* CPromptEng::FinalRelease */
  91. /////////////////////////////////////////////////////////////////////////////
  92. // CPromptEng::SetObjectToken
  93. //
  94. // Get all of the relevant registry information for this voice
  95. //
  96. //////////////////////////////////////////////////////////// JOEM 02-2000 //
  97. STDMETHODIMP CPromptEng::SetObjectToken(ISpObjectToken * pToken)
  98. {
  99. SPDBG_FUNC( "CPromptEng::SetObjectToken" );
  100. HRESULT hr = S_OK;
  101. ISpDataKey* pDataKey = NULL;
  102. WCHAR pszKey[USHRT_MAX] = L"";
  103. USHORT i = 0;
  104. CSpDynamicString dstrPath;
  105. CSpDynamicString dstrName;
  106. CSpDynamicString dstrTTSVoice;
  107. CSpDynamicString dstrGain;
  108. hr = SpGenericSetObjectToken(pToken, m_cpToken);
  109. // Get the associated TTS voice token, set the voice and output site
  110. if ( SUCCEEDED( hr ) )
  111. {
  112. hr = m_cpToken->GetStringValue( L"TTSVoice", &dstrName );
  113. if ( SUCCEEDED( hr ) && dstrName.Length() )
  114. {
  115. dstrTTSVoice = L"NAME=";
  116. dstrTTSVoice.Append(dstrName);
  117. CComPtr<IEnumSpObjectTokens> cpEnum;
  118. hr = SpEnumTokens( SPCAT_VOICES, dstrTTSVoice, NULL, &cpEnum );
  119. if( SUCCEEDED(hr) )
  120. {
  121. hr = cpEnum->Next( 1, &m_cpTTSToken, NULL );
  122. }
  123. // Any tokens?
  124. if ( hr != S_OK )
  125. {
  126. hr = E_INVALIDARG;
  127. }
  128. // Make sure the secondary voice is not another Prompt voice!
  129. if ( SUCCEEDED(hr) )
  130. {
  131. CSpDynamicString dstrTTSEngId;
  132. CSpDynamicString dstrPromptEngId = CLSID_PromptEng;
  133. hr = m_cpTTSToken->GetStringValue( L"CLSID", &dstrTTSEngId );
  134. if ( SUCCEEDED(hr) )
  135. {
  136. if ( !wcscmp(dstrPromptEngId, dstrTTSEngId) )
  137. {
  138. hr = E_INVALIDARG;
  139. }
  140. }
  141. dstrPromptEngId.Clear();
  142. dstrTTSEngId.Clear();
  143. }
  144. if ( SUCCEEDED(hr) )
  145. {
  146. hr = SpCreateObjectFromToken( m_cpTTSToken, &m_cpTTSEngine );
  147. }
  148. }
  149. dstrName.Clear();
  150. dstrTTSVoice.Clear();
  151. }
  152. // Allow no TTS Engine
  153. if ( FAILED(hr) )
  154. {
  155. //SPDBG_ASSERT(!m_cpTTSEngine);
  156. hr = S_FALSE;
  157. }
  158. // Gain factor for Prompt Entry output
  159. if ( SUCCEEDED( hr ) )
  160. {
  161. hr = m_cpToken->GetStringValue( L"PromptGain", &dstrGain );
  162. if ( SUCCEEDED(hr) )
  163. {
  164. hr = m_cpPromptDb->SetEntryGain( wcstod( dstrGain, NULL ) );
  165. }
  166. dstrGain.Clear();
  167. }
  168. // Load the text expansion/normalization rules
  169. // Implementation is script-language independent: language specified in registry entry.
  170. if ( SUCCEEDED( hr ) )
  171. {
  172. wcscpy(pszKey, L"PromptRules");
  173. hr = m_cpToken->OpenKey(pszKey, &pDataKey);
  174. if ( SUCCEEDED(hr) )
  175. {
  176. pDataKey->GetStringValue( L"Path", &dstrPath );
  177. pDataKey->GetStringValue( L"ScriptLanguage", &dstrName );
  178. if ( dstrName.Length() && dstrPath.Length() )
  179. {
  180. hr = m_textRules.ReadRules(dstrName, dstrPath);
  181. }
  182. dstrPath.Clear();
  183. dstrName.Clear();
  184. pDataKey->Release();
  185. pDataKey = NULL;
  186. }
  187. else
  188. {
  189. hr = S_FALSE; // allow no rules file
  190. }
  191. }
  192. // Load the phone context file. (Might be able to do this from language id instead of registry entry.)
  193. if ( SUCCEEDED( hr ) )
  194. {
  195. hr = m_cpToken->GetStringValue( L"PhContext", &dstrPath );
  196. if ( SUCCEEDED ( hr ) )
  197. {
  198. if ( wcscmp( dstrPath, L"DEFAULT" ) == 0 )
  199. {
  200. hr = m_phoneContext.LoadDefault();
  201. }
  202. else
  203. {
  204. hr = m_phoneContext.Load(dstrPath);
  205. }
  206. }
  207. else
  208. {
  209. hr = m_phoneContext.LoadDefault();
  210. }
  211. dstrPath.Clear();
  212. }
  213. // Get all of the Db Entries from the registry for this voice, add them to the list
  214. while ( SUCCEEDED ( hr ) )
  215. {
  216. CSpDynamicString dstrID;
  217. swprintf( pszKey, L"PromptData%hu", i );
  218. hr = m_cpToken->OpenKey(pszKey, &pDataKey);
  219. if ( i > 0 && hr == SPERR_NOT_FOUND )
  220. {
  221. hr = S_OK;
  222. break;
  223. }
  224. if ( SUCCEEDED(hr) )
  225. {
  226. hr = pDataKey->GetStringValue( L"Path", &dstrPath );
  227. if ( FAILED(hr) || !wcslen(dstrPath) )
  228. {
  229. hr = S_FALSE; // empty keys are just skipped.
  230. }
  231. }
  232. if ( hr == S_OK )
  233. {
  234. hr = pDataKey->GetStringValue( L"Name", &dstrName );
  235. if ( FAILED(hr) || !wcslen(dstrName) )
  236. {
  237. dstrName = L"DEFAULT";
  238. hr = S_OK;
  239. }
  240. }
  241. if ( hr == S_OK )
  242. {
  243. pDataKey->GetStringValue( L"ID Set", &dstrID ); // might not exist
  244. }
  245. if ( hr == S_OK )
  246. {
  247. WStringUpperCase(dstrName);
  248. if ( dstrID )
  249. {
  250. WStringUpperCase(dstrID);
  251. }
  252. hr = m_cpPromptDb->AddDb( dstrName, dstrPath, dstrID, DB_LOAD);
  253. }
  254. if ( SUCCEEDED ( hr ) )
  255. {
  256. i++;
  257. }
  258. dstrPath.Clear();
  259. dstrName.Clear();
  260. dstrID.Clear();
  261. if ( pDataKey )
  262. {
  263. pDataKey->Release();
  264. pDataKey = NULL;
  265. }
  266. }
  267. // Activate the default Db (or first Db, if "DEFAULT" doesn't exist)
  268. if ( SUCCEEDED(hr) )
  269. {
  270. hr = m_cpPromptDb->ActivateDbName(L"DEFAULT");
  271. if ( FAILED (hr) )
  272. {
  273. hr = m_cpPromptDb->ActivateDbNumber(0);
  274. }
  275. }
  276. // Initialize the Db Searching class
  277. if ( SUCCEEDED( hr ) )
  278. {
  279. hr = m_DbQuery.Init( m_cpPromptDb.p, &m_phoneContext );
  280. }
  281. if ( SUCCEEDED(hr) )
  282. {
  283. hr = m_pOutputSite->SetBufferSize(20);
  284. }
  285. SPDBG_REPORT_ON_FAIL( hr );
  286. return hr;
  287. }
  288. /////////////////////////////////////////////////////////////////////////////
  289. // CPromptEng::GetOutputFormat
  290. //
  291. // Gets the output format for the prompts and TTS.
  292. //
  293. //////////////////////////////////////////////////////////// JOEM 02-2000 //
  294. STDMETHODIMP CPromptEng::GetOutputFormat( const GUID * pTargetFormatId,
  295. const WAVEFORMATEX * pTargetWaveFormatEx,
  296. GUID * pDesiredFormatId,
  297. WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx )
  298. {
  299. SPDBG_FUNC( "CPromptEng::GetOutputFormat" );
  300. HRESULT hr = S_OK;
  301. SPDBG_ASSERT(m_cpPromptDb);
  302. if( ( SP_IS_BAD_WRITE_PTR(pDesiredFormatId) ) ||
  303. ( SP_IS_BAD_WRITE_PTR(ppCoMemDesiredWaveFormatEx) ) )
  304. {
  305. hr = E_INVALIDARG;
  306. }
  307. if ( SUCCEEDED(hr) )
  308. {
  309. // Use TTS preferred format if available
  310. if ( m_cpTTSEngine )
  311. {
  312. hr = m_cpTTSEngine->GetOutputFormat( pTargetFormatId, pTargetWaveFormatEx, pDesiredFormatId, ppCoMemDesiredWaveFormatEx );
  313. }
  314. else if ( m_cpPromptDb )
  315. {
  316. // otherwise, agree to text format if requested...
  317. if ( pTargetFormatId && *pTargetFormatId == SPDFID_Text )
  318. {
  319. *pDesiredFormatId = SPDFID_Text;
  320. *ppCoMemDesiredWaveFormatEx = NULL;
  321. }
  322. // ...or use prompt format.
  323. else
  324. {
  325. hr = m_cpPromptDb->GetPromptFormat(ppCoMemDesiredWaveFormatEx);
  326. if ( SUCCEEDED(hr) )
  327. {
  328. *pDesiredFormatId = SPDFID_WaveFormatEx;
  329. }
  330. }
  331. }
  332. }
  333. if ( SUCCEEDED(hr) )
  334. {
  335. if ( pDesiredFormatId )
  336. {
  337. hr = m_pOutputSite->SetOutputFormat( pDesiredFormatId, *ppCoMemDesiredWaveFormatEx );
  338. if ( SUCCEEDED(hr) )
  339. {
  340. hr = m_cpPromptDb->SetOutputFormat( pDesiredFormatId, *ppCoMemDesiredWaveFormatEx );
  341. }
  342. }
  343. else
  344. {
  345. hr = m_pOutputSite->SetOutputFormat( pTargetFormatId, pTargetWaveFormatEx );
  346. if ( SUCCEEDED(hr) )
  347. {
  348. hr = m_cpPromptDb->SetOutputFormat( pTargetFormatId, pTargetWaveFormatEx );
  349. }
  350. }
  351. }
  352. SPDBG_REPORT_ON_FAIL( hr );
  353. return hr;
  354. }
  355. /////////////////////////////////////////////////////////////////////////////
  356. // CPromptEng::Speak
  357. //
  358. // Builds a Db Query list from the frag list, and dispatches the fragments
  359. // to prompts or tts accordingly.
  360. //
  361. //////////////////////////////////////////////////////////// JOEM 02-2000 //
  362. STDMETHODIMP CPromptEng::Speak(DWORD dwSpeakFlags,
  363. REFGUID rguidFormatId,
  364. const WAVEFORMATEX * pWaveFormatEx,
  365. const SPVTEXTFRAG* pTextFragList,
  366. ISpTTSEngineSite* pOutputSite)
  367. {
  368. SPDBG_FUNC( "CPromptEng::Speak" );
  369. HRESULT hr = S_OK;
  370. m_fAbort = false;
  371. //--- Check args
  372. if( SP_IS_BAD_INTERFACE_PTR( pOutputSite ) ||
  373. SP_IS_BAD_READ_PTR( pTextFragList ) ||
  374. ( dwSpeakFlags & SPF_UNUSED_FLAGS ) )
  375. {
  376. return E_INVALIDARG;
  377. }
  378. if ( m_pOutputSite )
  379. {
  380. m_pOutputSite->SetOutputSite( pOutputSite );
  381. }
  382. if ( SUCCEEDED (hr) )
  383. {
  384. hr = BuildQueryList( dwSpeakFlags, pTextFragList, SAPI_FRAG );
  385. }
  386. //DebugQueryList();
  387. if ( SUCCEEDED(hr) && !m_fAbort )
  388. {
  389. hr = CompressQueryList();
  390. }
  391. //DebugQueryList();
  392. if ( SUCCEEDED(hr) && !m_fAbort )
  393. {
  394. hr = m_DbQuery.Query( &m_apQueries, &m_dQueryCost, m_pOutputSite, &m_fAbort );
  395. }
  396. //DebugQueryList();
  397. if ( SUCCEEDED(hr) && !m_fAbort )
  398. {
  399. hr = CompressTTSItems(rguidFormatId);
  400. }
  401. //DebugQueryList();
  402. if ( SUCCEEDED (hr) && !m_fAbort )
  403. {
  404. hr = DispatchQueryList(dwSpeakFlags, rguidFormatId, pWaveFormatEx);
  405. }
  406. // Successful or not, delete the query list
  407. for ( int i=0; i<m_apQueries.GetSize(); i++ )
  408. {
  409. if ( m_apQueries[i] )
  410. {
  411. delete m_apQueries[i];
  412. m_apQueries[i] = NULL;
  413. }
  414. }
  415. m_apQueries.RemoveAll();
  416. m_dQueryCost = 0.0;
  417. SPDBG_REPORT_ON_FAIL( hr );
  418. return hr;
  419. }
  420. /////////////////////////////////////////////////////////////////////////////
  421. // CPromptEng::BuildQueryList
  422. //
  423. // Steps through the frag list, handling known XML tags, and builds a list
  424. // of CQuery items. This function is pseudo-recursive: If a text expansion
  425. // rule modifies a text frag, this calls CPromptEng::ParseSubQuery to build a
  426. // secondary fragment list. ParseSubQuery then calls this function again.
  427. //
  428. //////////////////////////////////////////////////////////// JOEM 02-2000 //
  429. STDMETHODIMP CPromptEng::BuildQueryList(const DWORD dwSpeakFlags, const SPVTEXTFRAG* pCurrentFrag, const FragType fFragType)
  430. {
  431. SPDBG_FUNC( "CPromptEng::BuildQueryList" );
  432. HRESULT hr = S_OK;
  433. USHORT i = 0;
  434. USHORT subQueries = 0;
  435. bool fTTSOnly = false;
  436. bool fRuleDone = false;
  437. CQuery* pQuery = NULL;
  438. WCHAR* pszRuleName = NULL;
  439. CSPArray<CDynStr,CDynStr> aTags;
  440. if ( SP_IS_BAD_READ_PTR( pCurrentFrag ) )
  441. {
  442. return E_INVALIDARG;
  443. }
  444. while ( pCurrentFrag && SUCCEEDED(hr) )
  445. {
  446. // Stop speaking?
  447. if ( m_pOutputSite->GetActions() & SPVES_ABORT )
  448. {
  449. m_fAbort = true;
  450. break;
  451. }
  452. if ( SUCCEEDED(hr) )
  453. {
  454. // If there is nothing in this frag, just go immediately to next one
  455. if ( pCurrentFrag->State.eAction == SPVA_Speak && !pCurrentFrag->ulTextLen )
  456. {
  457. // Go to the next frag
  458. pCurrentFrag = pCurrentFrag->pNext;
  459. continue;
  460. }
  461. }
  462. if ( SUCCEEDED(hr) )
  463. {
  464. // Otherwise, create a new query
  465. pQuery = new CQuery;
  466. if ( !pQuery )
  467. {
  468. hr = E_OUTOFMEMORY;
  469. }
  470. }
  471. if ( SUCCEEDED(hr) )
  472. {
  473. pQuery->m_fFragType = fFragType;
  474. // Apply a rule to expand the text, if necessary
  475. if ( pszRuleName && !fRuleDone)
  476. {
  477. fRuleDone = true;
  478. WCHAR* pszOriginalText = (WCHAR*) malloc( sizeof(WCHAR) * (pCurrentFrag->ulTextLen + 1) );
  479. if ( !pszOriginalText )
  480. {
  481. hr = E_OUTOFMEMORY;
  482. }
  483. if ( SUCCEEDED(hr) )
  484. {
  485. wcsncpy(pszOriginalText, pCurrentFrag->pTextStart, pCurrentFrag->ulTextLen);
  486. pszOriginalText[pCurrentFrag->ulTextLen] = L'\0';
  487. hr = m_textRules.ApplyRule( pszRuleName, pszOriginalText, &pQuery->m_pszExpandedText );
  488. if ( SUCCEEDED(hr) )
  489. {
  490. free(pszOriginalText);
  491. pszOriginalText = NULL;
  492. USHORT currListSize = (USHORT) m_apQueries.GetSize();
  493. hr = ParseSubQuery( dwSpeakFlags, pQuery->m_pszExpandedText, &subQueries );
  494. // Adjust the text offset and len here. ParseSubQuery needs the len of the modified
  495. // text, but later, the app will need the offset & len of the original text for highlighting it.
  496. for ( i=currListSize; i<m_apQueries.GetSize(); i++ )
  497. {
  498. m_apQueries[i]->m_ulTextOffset = pCurrentFrag->ulTextSrcOffset;
  499. m_apQueries[i]->m_ulTextLen = pCurrentFrag->ulTextLen;
  500. }
  501. // Prefer to speak the subqueries rather than the orig frag.
  502. // But keep the orig, in case subq's fail.
  503. if ( subQueries )
  504. {
  505. pQuery->m_fSpeak = false;
  506. } // if ( !subQueries )
  507. }
  508. else // If the rule application fails, just use the original text.
  509. {
  510. hr = S_OK;
  511. //SPDBG_ASSERT( ! "Rule doesn't exist." );
  512. pQuery->m_pszExpandedText = pszOriginalText;
  513. }
  514. }
  515. }
  516. else // if no expansion rule, set the expanded text to the original text
  517. {
  518. pQuery->m_pszExpandedText = (WCHAR*) malloc( sizeof(WCHAR) * (pCurrentFrag->ulTextLen + 1));
  519. if ( !pQuery->m_pszExpandedText )
  520. {
  521. hr = E_OUTOFMEMORY;
  522. }
  523. if ( SUCCEEDED(hr) )
  524. {
  525. wcsncpy(pQuery->m_pszExpandedText, pCurrentFrag->pTextStart, pCurrentFrag->ulTextLen);
  526. pQuery->m_pszExpandedText[pCurrentFrag->ulTextLen] = L'\0';
  527. }
  528. } // else
  529. } // if ( SUCCEEDED(hr) )
  530. // Unicode control chars map to space
  531. if ( SUCCEEDED(hr) && pQuery->m_pszExpandedText )
  532. {
  533. WCHAR* psz = pQuery->m_pszExpandedText;
  534. while ( ( psz = FindUnicodeControlChar(psz) ) != NULL )
  535. {
  536. psz[0] = L' ';
  537. }
  538. }
  539. // HANDLE XML IN THE TEXT FRAG
  540. if ( SUCCEEDED(hr) )
  541. {
  542. // Is there an unknown tag is this fragment?
  543. if ( pCurrentFrag->State.eAction == SPVA_ParseUnknownTag )
  544. {
  545. CXMLTag unknownTag;
  546. hr = unknownTag.ParseUnknownTag( pQuery->m_pszExpandedText );
  547. // if this is a new RULE, set flag to process it.
  548. if ( SUCCEEDED(hr) )
  549. {
  550. const WCHAR* pszName = NULL;
  551. hr = unknownTag.GetTagName(&pszName);
  552. if ( wcscmp(pszName, XMLTags[RULE_END].pszTag) == 0 )
  553. {
  554. fRuleDone = false;
  555. }
  556. if ( SUCCEEDED (hr) )
  557. {
  558. hr = unknownTag.ApplyXML( pQuery, fTTSOnly, pszRuleName, aTags );
  559. if ( hr == S_FALSE )
  560. {
  561. // Don't know this XML -- must ignore or send it to TTS
  562. if ( !fTTSOnly )
  563. {
  564. pQuery->m_afEntryMatch.Add(false);
  565. }
  566. else
  567. {
  568. pQuery->m_afEntryMatch.Add(true);
  569. }
  570. }
  571. }
  572. else
  573. {
  574. pQuery->m_fSpeak = false;
  575. hr = S_OK;
  576. }
  577. }
  578. } // if ( pCurrentFrag->State.eAction == SPVA_ParseUnknownTag )
  579. else if ( pCurrentFrag->State.eAction == SPVA_Bookmark )
  580. {
  581. pQuery->m_fXML = UNKNOWN_XML;
  582. if ( !fTTSOnly )
  583. {
  584. pQuery->m_afEntryMatch.Add(false);
  585. }
  586. else
  587. {
  588. pQuery->m_afEntryMatch.Add(true);
  589. }
  590. }
  591. else if ( pCurrentFrag->State.eAction == SPVA_Silence )
  592. {
  593. pQuery->m_fXML = SILENCE;
  594. if ( !fTTSOnly )
  595. {
  596. pQuery->m_afEntryMatch.Add(false);
  597. }
  598. else
  599. {
  600. pQuery->m_afEntryMatch.Add(true);
  601. }
  602. }
  603. } // if ( SUCCEEDED(hr) )
  604. // Finish setting up this query item
  605. // We need a copy of the text frag, so we can break the links
  606. // when inserting new frags based on text expansion rules.
  607. if ( SUCCEEDED(hr) )
  608. {
  609. pQuery->m_pTextFrag = new SPVTEXTFRAG(*pCurrentFrag);
  610. if ( !pQuery->m_pTextFrag )
  611. {
  612. hr = E_OUTOFMEMORY;
  613. }
  614. if ( SUCCEEDED(hr) )
  615. {
  616. pQuery->m_pTextFrag->pNext = NULL;
  617. pQuery->m_ulTextOffset = pCurrentFrag->ulTextSrcOffset;
  618. pQuery->m_ulTextLen = pCurrentFrag->ulTextLen;
  619. // Use TTS if:
  620. // - it's a TTS item
  621. // - text is too long
  622. // - need to speak punctuation
  623. if ( (dwSpeakFlags & SPF_NLP_SPEAK_PUNC)
  624. || fTTSOnly
  625. || pQuery->m_pTextFrag->ulTextLen > MAX_SEARCH_LEN )
  626. {
  627. pQuery->m_fTTS = true;
  628. }
  629. // This keeps track of WITHTAG tags
  630. if ( aTags.GetSize() )
  631. {
  632. pQuery->m_paTagList = new CSPArray<CDynStr,CDynStr>;
  633. if ( !pQuery->m_paTagList )
  634. {
  635. hr = E_OUTOFMEMORY;
  636. }
  637. if ( SUCCEEDED(hr) )
  638. {
  639. for ( i=0; i < aTags.GetSize(); i++)
  640. {
  641. pQuery->m_paTagList->Add(aTags[i]);
  642. }
  643. }
  644. }
  645. // If this is a TTS item, make sure there is a TTS engine
  646. if ( pQuery->m_fTTS && !m_cpTTSEngine )
  647. {
  648. hr = PEERR_NO_TTS_VOICE;
  649. }
  650. // Add it to the query list
  651. m_apQueries.Add(pQuery);
  652. pQuery = NULL;
  653. subQueries = 0;
  654. // Go to the next frag
  655. pCurrentFrag = pCurrentFrag->pNext;
  656. }
  657. }
  658. } // while ( pCurrentFrag )
  659. // MEMORY CLEANUP ON ERROR
  660. if ( FAILED(hr) )
  661. {
  662. if ( pszRuleName )
  663. {
  664. free(pszRuleName);
  665. pszRuleName = NULL;
  666. }
  667. if ( pQuery )
  668. {
  669. delete pQuery;
  670. pQuery = NULL;
  671. }
  672. for ( i=0; i<aTags.GetSize(); i++ )
  673. {
  674. aTags[i].dstr.Clear();
  675. }
  676. aTags.RemoveAll();
  677. for ( i=0; i<m_apQueries.GetSize(); i++ )
  678. {
  679. if ( m_apQueries[i] )
  680. {
  681. delete m_apQueries[i];
  682. m_apQueries[i] = NULL;
  683. }
  684. }
  685. m_apQueries.RemoveAll();
  686. }
  687. // This makes sure we don't leak memory if user forgets to close <RULE> </RULE> tag.
  688. if ( pszRuleName )
  689. {
  690. free(pszRuleName);
  691. pszRuleName = NULL;
  692. }
  693. SPDBG_REPORT_ON_FAIL( hr );
  694. return hr;
  695. }
  696. /////////////////////////////////////////////////////////////////////////////
  697. // CPromptEng::ParseSubQuery
  698. //
  699. // When a text expansion rule modifies a text fragment, it may insert new
  700. // XML tags that need to be added to the fragment list. This function is
  701. // called to build a new fragment list out of the modified text (which may
  702. // or may not include new XML tags). This function then calls
  703. // CPromptEng::BuildQueryList to add CQueries based on the new fragment list.
  704. //
  705. //////////////////////////////////////////////////////////// JOEM 04-2000 //
  706. STDMETHODIMP CPromptEng::ParseSubQuery(const DWORD dwSpeakFlags, const WCHAR *pszText, USHORT* unSubQueries)
  707. {
  708. SPDBG_FUNC( "CPromptEng::ParseSubQuery" );
  709. HRESULT hr = S_OK;
  710. const WCHAR* p = NULL;
  711. const WCHAR* pTagStart = NULL;
  712. const WCHAR* pTagEnd = NULL;
  713. const WCHAR* pStart = NULL;
  714. SPVTEXTFRAG* pNextFrag = NULL;
  715. SPVTEXTFRAG* pFrag = NULL;
  716. SPVTEXTFRAG* pFirstFrag = NULL;
  717. SPDBG_ASSERT(pszText);
  718. // need a copy of this text
  719. pStart = pszText;
  720. p = pszText;
  721. WSkipWhiteSpace(pStart);
  722. WSkipWhiteSpace(pStart);
  723. // Find an XML tag
  724. while ( pTagStart = wcschr(p, L'<') )
  725. {
  726. // if the first char is a tag, make a frag out of it
  727. if ( pTagStart == p )
  728. {
  729. pTagEnd = wcschr(pTagStart, L'>');
  730. if ( !pTagEnd )
  731. {
  732. hr = E_INVALIDARG;
  733. }
  734. // Create a new SPVTEXTFRAG consisting of just this XML tag.
  735. if ( SUCCEEDED(hr) )
  736. {
  737. pNextFrag = new SPVTEXTFRAG;
  738. if ( !pNextFrag )
  739. {
  740. hr = E_OUTOFMEMORY;
  741. }
  742. }
  743. if ( SUCCEEDED(hr) )
  744. {
  745. memset( pNextFrag, 0, sizeof (SPVTEXTFRAG) );
  746. memset( &pNextFrag->State, 0, sizeof (SPVSTATE) );
  747. pNextFrag->pTextStart = pTagStart;
  748. pNextFrag->ulTextLen = pTagEnd - pTagStart + 1;
  749. pNextFrag->State.eAction = SPVA_ParseUnknownTag;
  750. p = ++pTagEnd;
  751. (*unSubQueries)++;
  752. }
  753. }
  754. // if some text was skipped when searching for '<',
  755. // make a frag out of the skipped text.
  756. else
  757. {
  758. pNextFrag = new SPVTEXTFRAG;
  759. if ( !pNextFrag )
  760. {
  761. hr = E_OUTOFMEMORY;
  762. }
  763. if ( SUCCEEDED(hr) )
  764. {
  765. memset( pNextFrag, 0, sizeof(SPVTEXTFRAG) );
  766. memset( &pNextFrag->State, 0, sizeof (SPVSTATE) );
  767. pNextFrag->pTextStart = p;
  768. pNextFrag->ulTextLen = pTagStart - p;
  769. pNextFrag->State.eAction = SPVA_Speak;
  770. p = pTagStart;
  771. (*unSubQueries)++;
  772. }
  773. }
  774. if ( SUCCEEDED(hr) )
  775. {
  776. // Was this the first fragment?
  777. if ( pFrag )
  778. {
  779. pFrag->pNext = pNextFrag;
  780. }
  781. else
  782. {
  783. pFrag = pNextFrag;
  784. pFirstFrag = pFrag;
  785. }
  786. // Move on to next one
  787. pFrag = pNextFrag;
  788. pTagStart = NULL;
  789. pTagEnd = NULL;
  790. pNextFrag = NULL;
  791. WSkipWhiteSpace(p);
  792. }
  793. }
  794. // If NO tags were found, don't need to create any fragments.
  795. if ( pFirstFrag && SUCCEEDED (hr) )
  796. {
  797. // When done finding tags, check for more text and make a fragment out of it.
  798. if ( p < pStart + wcslen(pStart) )
  799. {
  800. pNextFrag = new SPVTEXTFRAG;
  801. if ( !pNextFrag )
  802. {
  803. hr = E_OUTOFMEMORY;
  804. }
  805. if ( SUCCEEDED(hr) )
  806. {
  807. memset( pNextFrag, 0, sizeof(SPVTEXTFRAG) );
  808. memset( &pNextFrag->State, 0, sizeof (SPVSTATE) );
  809. pNextFrag->pTextStart = p;
  810. pNextFrag->ulTextLen = pStart + wcslen(pStart) - p;
  811. pNextFrag->State.eAction = SPVA_Speak;
  812. pFrag->pNext = pNextFrag;
  813. (*unSubQueries)++;
  814. }
  815. }
  816. hr = BuildQueryList( dwSpeakFlags, pFirstFrag, LOCAL_FRAG );
  817. }
  818. pFrag = pFirstFrag;
  819. while ( pFrag )
  820. {
  821. pNextFrag = pFrag->pNext;
  822. delete pFrag;
  823. pFrag = pNextFrag;
  824. }
  825. SPDBG_REPORT_ON_FAIL( hr );
  826. return hr;
  827. }
  828. /////////////////////////////////////////////////////////////////////////////
  829. // CPromptEng::CompressQueryList
  830. //
  831. // Adjacent Db Search items are combined (before search).
  832. //
  833. // Note that there may be intervening XML between search items.
  834. // If intervening XML is unknown:
  835. // - ignore it and go to the next search item,
  836. // - but save it in case the search fails (so it can be restored and
  837. // passed on to TTS).
  838. // If intervening XML is KNOWN, stop combining, so XML can be processed
  839. // during the search.
  840. //
  841. //////////////////////////////////////////////////////////// JOEM 06-2000 //
  842. STDMETHODIMP CPromptEng::CompressQueryList()
  843. {
  844. SPDBG_FUNC( "CPromptEng::CompressQueryList" );
  845. HRESULT hr = S_OK;
  846. CQuery* pCurrentQuery = NULL;
  847. CQuery* pPreviousQuery = NULL;
  848. USHORT unPrevSize = 0;
  849. USHORT unCurrSize = 0;
  850. USHORT i = 0;
  851. int j = 0;
  852. for ( i = 0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ )
  853. {
  854. pPreviousQuery = pCurrentQuery;
  855. while ( SUCCEEDED(hr) && i < m_apQueries.GetSize() )
  856. {
  857. if ( SUCCEEDED(hr) )
  858. {
  859. pCurrentQuery = m_apQueries[i];
  860. SPDBG_ASSERT(pCurrentQuery);
  861. // Don't combine if either query:
  862. // - is not marked "Speak" or
  863. // - is marked TTS or
  864. // - is an ID or
  865. // - is just an SPVSTATE (no m_pszExpandedText) or
  866. // - is known XML or
  867. // - is silence
  868. // - has tags or
  869. // - is a local frag or
  870. // - (special case) there is a volume change
  871. // - (special case) there is a rate change
  872. if (!pPreviousQuery ||
  873. !pPreviousQuery->m_fSpeak ||
  874. pPreviousQuery->m_fTTS ||
  875. pPreviousQuery->m_pszId ||
  876. !pPreviousQuery->m_pszExpandedText ||
  877. pPreviousQuery->m_fXML == KNOWN_XML ||
  878. pPreviousQuery->m_fXML == SILENCE ||
  879. pPreviousQuery->m_paTagList ||
  880. pPreviousQuery->m_fFragType == LOCAL_FRAG ||
  881. !pCurrentQuery->m_fSpeak ||
  882. pCurrentQuery->m_fTTS ||
  883. pCurrentQuery->m_pszId ||
  884. !pCurrentQuery->m_pszExpandedText ||
  885. pCurrentQuery->m_fXML == KNOWN_XML ||
  886. pCurrentQuery->m_fXML == SILENCE ||
  887. pCurrentQuery->m_paTagList ||
  888. pCurrentQuery->m_fFragType == LOCAL_FRAG ||
  889. pPreviousQuery->m_pTextFrag->State.Volume != pCurrentQuery->m_pTextFrag->State.Volume ||
  890. pPreviousQuery->m_pTextFrag->State.RateAdj != pCurrentQuery->m_pTextFrag->State.RateAdj ||
  891. // And, don't combine it with any previous unknown XML.
  892. ( pPreviousQuery->m_fFragType != COMBINED_FRAG && pPreviousQuery->m_fXML == UNKNOWN_XML )
  893. )
  894. {
  895. // Don't combine this item? Then restore all immediately preceding unknown XML tags.
  896. for ( j=i-1; j>=0; j-- )
  897. {
  898. if ( m_apQueries[j]->m_fXML == UNKNOWN_XML )
  899. {
  900. m_apQueries[j]->m_fFragType = SAPI_FRAG;
  901. m_apQueries[j]->m_fSpeak = true;
  902. }
  903. else
  904. {
  905. break;
  906. }
  907. }
  908. break;
  909. }
  910. // Combine search items.
  911. pPreviousQuery->m_fFragType = COMBINED_FRAG;
  912. // adjust the length - this is complicated because unknown tags in text aren't processed here
  913. ULONG OffsetFromFirstPrompt = pCurrentQuery->m_ulTextOffset - pPreviousQuery->m_ulTextOffset;
  914. pPreviousQuery->m_ulTextLen = OffsetFromFirstPrompt + pCurrentQuery->m_ulTextLen;
  915. // Unknown XML? don't really combine - just mark it out and skip to next one
  916. if ( pCurrentQuery->m_fXML == UNKNOWN_XML )
  917. {
  918. pCurrentQuery->m_fFragType = COMBINED_FRAG;
  919. pCurrentQuery->m_fSpeak = false;
  920. i++;
  921. continue;
  922. }
  923. // Append current text to previous
  924. unPrevSize = sizeof(pPreviousQuery->m_pszExpandedText) * wcslen(pPreviousQuery->m_pszExpandedText);
  925. unCurrSize = sizeof(pCurrentQuery->m_pszExpandedText) * wcslen(pCurrentQuery->m_pszExpandedText);
  926. pPreviousQuery->m_pszExpandedText = (WCHAR*) realloc( pPreviousQuery->m_pszExpandedText,
  927. unPrevSize + unCurrSize + sizeof(WCHAR) );
  928. if ( !pPreviousQuery->m_pszExpandedText )
  929. {
  930. hr = E_OUTOFMEMORY;
  931. }
  932. if ( SUCCEEDED(hr) )
  933. {
  934. wcscat(pPreviousQuery->m_pszExpandedText, L" ");
  935. wcscat(pPreviousQuery->m_pszExpandedText, pCurrentQuery->m_pszExpandedText);
  936. }
  937. if ( SUCCEEDED(hr) )
  938. {
  939. pCurrentQuery->m_fFragType = COMBINED_FRAG;
  940. pCurrentQuery->m_fSpeak = false;
  941. i++;
  942. }
  943. } // if ( SUCCEEDED(hr) )
  944. } // while
  945. } // for
  946. SPDBG_REPORT_ON_FAIL( hr );
  947. return hr;
  948. }
  949. /////////////////////////////////////////////////////////////////////////////
  950. // CPromptEng::CompressTTSItems
  951. //
  952. // Adjacent TTS items are combined (after search).
  953. //
  954. //////////////////////////////////////////////////////////// JOEM 06-2000 //
  955. STDMETHODIMP CPromptEng::CompressTTSItems(REFGUID rguidFormatId)
  956. {
  957. SPDBG_FUNC( "CPromptEng::CompressTTSItems" );
  958. HRESULT hr = S_OK;
  959. CQuery* pCurrentQuery = NULL;
  960. CQuery* pPreviousQuery = NULL;
  961. SPVTEXTFRAG* pFrag = NULL;
  962. USHORT i = 0;
  963. for ( i = 0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ )
  964. {
  965. while ( SUCCEEDED(hr) && i < m_apQueries.GetSize() )
  966. {
  967. pCurrentQuery = m_apQueries[i];
  968. SPDBG_ASSERT(pCurrentQuery);
  969. // If this is a TTS item, make sure there is a TTS engine
  970. if ( pCurrentQuery->m_fTTS && !m_cpTTSEngine )
  971. {
  972. // otherwise, if it's just unknown xml, ignore it
  973. if ( pCurrentQuery->m_fXML == UNKNOWN_XML )
  974. {
  975. pCurrentQuery->m_fSpeak = false;
  976. }
  977. else
  978. {
  979. hr = PEERR_NO_TTS_VOICE;
  980. }
  981. }
  982. if ( SUCCEEDED(hr) )
  983. {
  984. // Don't combine if either query:
  985. // - if the output format is text
  986. // - is marked "don't speak"
  987. // - is not TTS
  988. if ( rguidFormatId == SPDFID_Text ||
  989. !pPreviousQuery ||
  990. !pPreviousQuery->m_fSpeak ||
  991. !pPreviousQuery->m_fTTS ||
  992. !pCurrentQuery->m_fSpeak ||
  993. !pCurrentQuery->m_fTTS
  994. )
  995. {
  996. if ( pCurrentQuery->m_fSpeak )
  997. {
  998. pPreviousQuery = pCurrentQuery;
  999. }
  1000. break;
  1001. }
  1002. pFrag = pPreviousQuery->m_pTextFrag;
  1003. while ( pFrag->pNext )
  1004. {
  1005. pFrag = pFrag->pNext;
  1006. }
  1007. pFrag->pNext = pCurrentQuery->m_pTextFrag;
  1008. m_apQueries[i]->m_fSpeak = false;
  1009. i++;
  1010. }
  1011. } // while
  1012. } // for
  1013. SPDBG_REPORT_ON_FAIL( hr );
  1014. return hr;
  1015. }
  1016. /////////////////////////////////////////////////////////////////////////////
  1017. // CPromptEng::DispatchQueryList
  1018. //
  1019. // Goes through the query list, sending TTS or prompts as appropriate.
  1020. //
  1021. //////////////////////////////////////////////////////////// JOEM 02-2000 //
  1022. STDMETHODIMP CPromptEng::DispatchQueryList(const DWORD dwSpeakFlags, REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx)
  1023. {
  1024. SPDBG_FUNC( "CPromptEng::DispatchQueryList" );
  1025. HRESULT hr = S_OK;
  1026. USHORT i = 0;
  1027. SHORT j = 0;
  1028. CQuery* pQuery = NULL;
  1029. CPromptEntry* entry = NULL;
  1030. if ( rguidFormatId == SPDFID_Text )
  1031. {
  1032. hr = SendTextOutput( dwSpeakFlags, rguidFormatId );
  1033. }
  1034. else
  1035. {
  1036. for ( i=0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ )
  1037. {
  1038. // Stop speaking?
  1039. if ( m_pOutputSite->GetActions() & SPVES_ABORT )
  1040. {
  1041. m_fAbort = true;
  1042. break;
  1043. }
  1044. pQuery = m_apQueries[i];
  1045. if ( !pQuery->m_fSpeak )
  1046. {
  1047. continue;
  1048. }
  1049. if ( pQuery->m_fTTS )
  1050. {
  1051. // Should not get to this point without a TTS Engine!
  1052. SPDBG_ASSERT(m_cpTTSEngine);
  1053. // TTS items get passed directly to TTS Engine
  1054. if ( SUCCEEDED(hr) )
  1055. {
  1056. hr = m_cpTTSEngine->Speak(dwSpeakFlags, rguidFormatId, NULL, pQuery->m_pTextFrag, m_pOutputSite);
  1057. if ( SUCCEEDED(hr) )
  1058. {
  1059. m_pOutputSite->UpdateBytesWritten();
  1060. }
  1061. }
  1062. }
  1063. else
  1064. {
  1065. if ( SUCCEEDED(hr) && ( pQuery->m_fFragType != LOCAL_FRAG ) )
  1066. {
  1067. m_cpPromptDb->SetXMLVolume(pQuery->m_pTextFrag->State.Volume);
  1068. m_cpPromptDb->SetXMLRate(pQuery->m_pTextFrag->State.RateAdj);
  1069. }
  1070. if ( pQuery->m_fXML == SILENCE )
  1071. {
  1072. hr = SendSilence(pQuery->m_pTextFrag->State.SilenceMSecs, pWaveFormatEx->nAvgBytesPerSec);
  1073. }
  1074. else
  1075. {
  1076. // The search process backtracks the list, so these are in reverse order.
  1077. // So, play in last-to-first order.
  1078. for ( j=pQuery->m_apEntry.GetSize()-1; j>=0; j-- )
  1079. {
  1080. entry = pQuery->m_apEntry[j];
  1081. if ( !entry )
  1082. {
  1083. hr = E_FAIL;
  1084. }
  1085. if ( SUCCEEDED(hr) )
  1086. {
  1087. hr = m_cpPromptDb->SendEntrySamples(entry, m_pOutputSite, pQuery->m_ulTextOffset, pQuery->m_ulTextLen);
  1088. }
  1089. } // for ( j=pQuery->m_apEntry.GetSize()-1 ...
  1090. }
  1091. }
  1092. }
  1093. }
  1094. SPDBG_REPORT_ON_FAIL( hr );
  1095. return hr;
  1096. }
  1097. /////////////////////////////////////////////////////////////////////////////
  1098. // CPromptEng::SendSilence
  1099. //
  1100. // Generates silence and writes to output site.
  1101. //
  1102. //////////////////////////////////////////////////////////// JOEM 11-2000 //
  1103. STDMETHODIMP CPromptEng::SendSilence(const int iMsec, const DWORD iAvgBytesPerSec)
  1104. {
  1105. SPDBG_FUNC( "CPromptEng::SendSilence" );
  1106. HRESULT hr = S_OK;
  1107. BYTE* pbSilence = NULL;
  1108. int iBytes = iMsec/1000 * iAvgBytesPerSec;
  1109. if ( iBytes )
  1110. {
  1111. pbSilence = new BYTE[iBytes];
  1112. if ( !pbSilence )
  1113. {
  1114. hr = E_OUTOFMEMORY;
  1115. }
  1116. if ( SUCCEEDED(hr) )
  1117. {
  1118. memset( (void*) pbSilence, 0, iBytes * sizeof(BYTE) );
  1119. hr = m_pOutputSite->Write(pbSilence, iBytes, 0);
  1120. if ( SUCCEEDED(hr) )
  1121. {
  1122. m_pOutputSite->UpdateBytesWritten();
  1123. }
  1124. delete [] pbSilence;
  1125. pbSilence = NULL;
  1126. }
  1127. }
  1128. SPDBG_REPORT_ON_FAIL( hr );
  1129. return hr;
  1130. }
  1131. /////////////////////////////////////////////////////////////////////////////
  1132. // CPromptEng::SendTextOutput
  1133. //
  1134. // Output function for SPDFID_Text format. Goes through the query list,
  1135. // sending text from TTS or prompts as appropriate.
  1136. //
  1137. // This functionality is needed mainly for the test team.
  1138. //
  1139. //////////////////////////////////////////////////////////// JOEM 11-2000 //
  1140. STDMETHODIMP CPromptEng::SendTextOutput(const DWORD dwSpeakFlags, REFGUID rguidFormatId)
  1141. {
  1142. SPDBG_FUNC( "CPromptEng::SendTextOutput" );
  1143. HRESULT hr = S_OK;
  1144. USHORT i = 0;
  1145. SHORT j = 0;
  1146. CQuery* pQuery = NULL;
  1147. static const WCHAR Signature = 0xFEFF; // write the Unicode signature
  1148. hr = m_pOutputSite->Write( &Signature, sizeof(Signature), NULL );
  1149. for ( i=0; SUCCEEDED(hr) && i < m_apQueries.GetSize(); i++ )
  1150. {
  1151. // Stop speaking?
  1152. if ( m_pOutputSite->GetActions() & SPVES_ABORT )
  1153. {
  1154. m_fAbort = true;
  1155. break;
  1156. }
  1157. pQuery = m_apQueries[i];
  1158. if ( !pQuery->m_fSpeak )
  1159. {
  1160. continue;
  1161. }
  1162. if ( pQuery->m_fTTS )
  1163. {
  1164. // Should not get to this point without a TTS Engine!
  1165. SPDBG_ASSERT(m_cpTTSEngine);
  1166. WCHAR szText[7] = L"0:"; // 7 chars for "0:TTS:" (6 plus \0)
  1167. if ( pQuery->m_afEntryMatch.GetSize() && pQuery->m_afEntryMatch[0] )
  1168. {
  1169. wcscpy(szText, L"1:");
  1170. }
  1171. wcscat(szText, L"TTS:");
  1172. hr = m_pOutputSite->Write( szText, (wcslen(szText) + 1) * sizeof(WCHAR), NULL );
  1173. // TTS items get passed directly to TTS Engine
  1174. if ( SUCCEEDED(hr) )
  1175. {
  1176. hr = m_cpTTSEngine->Speak(dwSpeakFlags, rguidFormatId, NULL, pQuery->m_pTextFrag, m_pOutputSite);
  1177. if ( SUCCEEDED(hr) )
  1178. {
  1179. m_pOutputSite->UpdateBytesWritten();
  1180. }
  1181. }
  1182. }
  1183. else
  1184. {
  1185. CPromptEntry* entry = NULL;
  1186. if ( SUCCEEDED(hr) && ( pQuery->m_fFragType != LOCAL_FRAG ) )
  1187. {
  1188. hr = m_cpPromptDb->SetXMLVolume(pQuery->m_pTextFrag->State.Volume);
  1189. }
  1190. // The search process backtracks the list, so these are in reverse order.
  1191. // So, play in last-to-first order.
  1192. for ( j=pQuery->m_apEntry.GetSize()-1; j>=0; j-- )
  1193. {
  1194. entry = pQuery->m_apEntry[j];
  1195. if ( !entry )
  1196. {
  1197. hr = E_FAIL;
  1198. }
  1199. if ( SUCCEEDED(hr) )
  1200. {
  1201. // For text output (TEST HOOKS)
  1202. WCHAR* pszOutput = NULL;
  1203. WCHAR* pszTagOutput = NULL;
  1204. WCHAR szMatch = L'0';
  1205. const WCHAR* pszId = NULL;
  1206. const WCHAR* pszText = NULL;
  1207. const WCHAR* pszTag = NULL;
  1208. int iLen = 0;
  1209. USHORT k = 0;
  1210. USHORT unTags = 0;
  1211. // get the tag match indicator
  1212. SPDBG_ASSERT(j < pQuery->m_afEntryMatch.GetSize());
  1213. if ( pQuery->m_afEntryMatch[j] )
  1214. {
  1215. szMatch = L'1';
  1216. }
  1217. // get the ID#, text, and tags -- and write them in the output
  1218. if ( SUCCEEDED(hr) )
  1219. {
  1220. // Get the ID
  1221. if ( SUCCEEDED(hr) )
  1222. {
  1223. hr = entry->GetId(&pszId);
  1224. }
  1225. // Get the Text
  1226. if ( SUCCEEDED(hr) )
  1227. {
  1228. hr = entry->GetText(&pszText);
  1229. }
  1230. // Assemble the collection of Tags
  1231. hr = entry->CountTags(&unTags);
  1232. if ( SUCCEEDED(hr) )
  1233. {
  1234. for ( k=0; SUCCEEDED(hr) && k<unTags; k++ )
  1235. {
  1236. hr = entry->GetTag(&pszTag, k);
  1237. if ( SUCCEEDED(hr) )
  1238. {
  1239. iLen += wcslen(pszTag);
  1240. iLen++; // +1 for the comma or \0
  1241. }
  1242. }
  1243. if ( iLen )
  1244. {
  1245. pszTagOutput = new WCHAR[iLen];
  1246. }
  1247. else
  1248. {
  1249. pszTagOutput = new WCHAR[2];
  1250. }
  1251. if ( !pszTagOutput )
  1252. {
  1253. hr = E_OUTOFMEMORY;
  1254. }
  1255. else
  1256. {
  1257. if ( !iLen )
  1258. {
  1259. wcscpy( pszTagOutput, L" " );
  1260. }
  1261. }
  1262. for ( k=0; SUCCEEDED(hr) && k<unTags; k++ )
  1263. {
  1264. hr = entry->GetTag(&pszTag, k);
  1265. if ( SUCCEEDED(hr) )
  1266. {
  1267. if ( k==0 )
  1268. {
  1269. wcscpy(pszTagOutput, pszTag);
  1270. }
  1271. else
  1272. {
  1273. wcscat(pszTagOutput, L",");
  1274. wcscat(pszTagOutput, pszTag);
  1275. }
  1276. }
  1277. }
  1278. }
  1279. // Put it all together
  1280. if ( SUCCEEDED(hr) )
  1281. {
  1282. if ( pszText && pszId )
  1283. {
  1284. iLen = wcslen(pszId) + wcslen(pszTagOutput) + wcslen(pszText) + wcslen(L"0:ID()():\r\n");
  1285. pszOutput = new WCHAR[iLen+1];
  1286. if ( !pszOutput )
  1287. {
  1288. hr = E_OUTOFMEMORY;
  1289. }
  1290. if ( SUCCEEDED(hr) )
  1291. {
  1292. // signal to the app that the upcoming output is from prompts, not TTS.
  1293. swprintf (pszOutput, L"%c:ID(%s)(%s):%s\r\n", szMatch, pszId, pszTagOutput, pszText);
  1294. hr = m_pOutputSite->Write(pszOutput, (wcslen(pszOutput) + 1) * sizeof(WCHAR), NULL);
  1295. }
  1296. }
  1297. else // no text/id, must be an XML-specified wav file
  1298. {
  1299. const WCHAR* pszPath = NULL;
  1300. hr = entry->GetFileName(&pszPath);
  1301. if ( SUCCEEDED(hr) )
  1302. {
  1303. // make sure that the file is actually wav data
  1304. VapiIO* pVapiIO = VapiIO::ClassFactory();
  1305. if ( !pVapiIO )
  1306. {
  1307. hr = E_OUTOFMEMORY;
  1308. }
  1309. if ( SUCCEEDED(hr) )
  1310. {
  1311. if ( pVapiIO->OpenFile(pszPath, VAPI_IO_READ) != 0 )
  1312. {
  1313. hr = E_ACCESSDENIED;
  1314. }
  1315. delete pVapiIO;
  1316. pVapiIO = NULL;
  1317. }
  1318. }
  1319. if ( SUCCEEDED(hr) )
  1320. {
  1321. iLen = wcslen(L"1:WAV()\r\n") + wcslen(pszPath) + 1;
  1322. pszOutput = new WCHAR[iLen];
  1323. swprintf (pszOutput, L"1:WAV(%s)\r\n", pszPath);
  1324. hr = m_pOutputSite->Write(pszOutput, (wcslen(pszOutput) + 1) * sizeof(WCHAR), NULL);
  1325. }
  1326. }
  1327. }
  1328. if ( pszOutput )
  1329. {
  1330. delete [] pszOutput;
  1331. pszOutput = NULL;
  1332. }
  1333. if ( pszTagOutput )
  1334. {
  1335. delete [] pszTagOutput;
  1336. pszTagOutput = NULL;
  1337. }
  1338. }
  1339. }
  1340. } // for ( j=pQuery->m_apEntry.GetSize()-1 ...
  1341. }
  1342. }
  1343. // TEST HOOK: OUTPUT THE PATH COST
  1344. if ( SUCCEEDED(hr) && !m_fAbort )
  1345. {
  1346. const iStrLen = 20; // arbitrary -- plenty of space for 6 dig precision, plus sign, exponent, etc if necessary
  1347. char szCost[iStrLen];
  1348. WCHAR wszCost[iStrLen];
  1349. WCHAR* pszOutput = NULL;
  1350. pszOutput = new WCHAR[iStrLen + wcslen(L"Cost: \r\n")];
  1351. if ( !pszOutput )
  1352. {
  1353. hr = E_OUTOFMEMORY;
  1354. }
  1355. if ( SUCCEEDED(hr) )
  1356. {
  1357. _gcvt( m_dQueryCost, 6, szCost ); // convert to string
  1358. if ( !MultiByteToWideChar( CP_ACP, 0, szCost, -1, wszCost, iStrLen ) )
  1359. {
  1360. hr = E_FAIL;
  1361. }
  1362. if ( SUCCEEDED(hr) )
  1363. {
  1364. swprintf (pszOutput, L"Cost: %.6s\r\n", wszCost );
  1365. hr = m_pOutputSite->Write(pszOutput, (wcslen(pszOutput) + 1) * sizeof(WCHAR), NULL);
  1366. }
  1367. }
  1368. if ( pszOutput )
  1369. {
  1370. delete [] pszOutput;
  1371. pszOutput = NULL;
  1372. }
  1373. }
  1374. SPDBG_REPORT_ON_FAIL( hr );
  1375. return hr;
  1376. }
  1377. /////////////////////////////////////////////////////////////////////////////
  1378. // CPromptEng::DebugQueryList
  1379. //
  1380. // Just outputs the contents of each CQuery in the query list.
  1381. //
  1382. //////////////////////////////////////////////////////////// JOEM 04-2000 //
  1383. void CPromptEng::DebugQueryList()
  1384. {
  1385. SPDBG_FUNC( "CPromptEng::DebugQueryList" );
  1386. USHORT i = 0;
  1387. USHORT j = 0;
  1388. USHORT k = 0;
  1389. CQuery* query = NULL;
  1390. CPromptEntry* entry = NULL;
  1391. double dEntryInfo = 0.0;
  1392. const WCHAR* szEntryInfo = NULL;
  1393. USHORT count = 0;
  1394. const USHORT MAX_FRAG = 1024;
  1395. WCHAR DebugStr[MAX_FRAG+1];
  1396. if ( !m_apQueries.GetSize() )
  1397. {
  1398. OutputDebugStringW(L"\nNO QUERY LIST!\n\n");
  1399. return;
  1400. }
  1401. for (i=0; i<m_apQueries.GetSize(); i++)
  1402. {
  1403. query = m_apQueries[i];
  1404. swprintf (DebugStr, L"Query %d:\n", i+1);
  1405. OutputDebugStringW(DebugStr);
  1406. if ( !query )
  1407. {
  1408. OutputDebugStringW(L"NULL QUERY!\n");
  1409. continue;
  1410. }
  1411. if ( query->m_fSpeak )
  1412. {
  1413. OutputDebugStringW(L"\tSpeak Flag = true\n");
  1414. }
  1415. else
  1416. {
  1417. OutputDebugStringW(L"\tSpeak Flag = false\n");
  1418. }
  1419. if ( query->m_fTTS )
  1420. {
  1421. OutputDebugStringW(L"\tTTS Flag = true\n");
  1422. }
  1423. else
  1424. {
  1425. OutputDebugStringW(L"\tTTS Flag = false\n");
  1426. }
  1427. if ( query->m_fXML == KNOWN_XML )
  1428. {
  1429. OutputDebugStringW(L"\tKNOWN XML\n");
  1430. }
  1431. else if ( query->m_fXML == UNKNOWN_XML )
  1432. {
  1433. OutputDebugStringW(L"\tUNKNOWN XML\n");
  1434. }
  1435. if ( query->m_fFragType == LOCAL_FRAG )
  1436. {
  1437. OutputDebugStringW(L"\tLOCAL FRAG\n");
  1438. }
  1439. if ( query->m_fFragType == COMBINED_FRAG )
  1440. {
  1441. OutputDebugStringW(L"\tCOMBINED FRAG\n");
  1442. }
  1443. OutputDebugStringW(L"\tText Frag: ");
  1444. if ( query->m_pTextFrag && query->m_pTextFrag->pTextStart )
  1445. {
  1446. wcsncpy(DebugStr, query->m_pTextFrag->pTextStart, 1024);
  1447. DebugStr[1024] = L'\0';
  1448. OutputDebugStringW(DebugStr);
  1449. OutputDebugStringW(L"\n");
  1450. }
  1451. OutputDebugStringW(L"\tExpanded Text: ");
  1452. if ( query->m_pszExpandedText )
  1453. {
  1454. OutputDebugStringW(query->m_pszExpandedText);
  1455. OutputDebugStringW(L"\n");
  1456. }
  1457. swprintf (DebugStr, L"\tDbAction: %d\n", query->m_unDbAction);
  1458. OutputDebugStringW(DebugStr);
  1459. swprintf (DebugStr, L"\tDbIndex: %d\n", query->m_unDbIndex);
  1460. OutputDebugStringW(DebugStr);
  1461. if ( query->m_pszDbName )
  1462. {
  1463. OutputDebugStringW(L"\tDbName: ");
  1464. OutputDebugStringW(query->m_pszDbName);
  1465. OutputDebugStringW(L"\n");
  1466. }
  1467. if ( query->m_pszDbPath )
  1468. {
  1469. OutputDebugStringW(L"\tDbPath: ");
  1470. OutputDebugStringW(query->m_pszDbPath);
  1471. OutputDebugStringW(L"\n");
  1472. }
  1473. OutputDebugStringW(L"\tID: ");
  1474. if ( query->m_pszId )
  1475. {
  1476. swprintf (DebugStr, L"%s\n", query->m_pszId);
  1477. OutputDebugStringW(DebugStr);
  1478. }
  1479. else
  1480. {
  1481. OutputDebugStringW(L"(none)\n");
  1482. }
  1483. if ( query->m_paTagList )
  1484. {
  1485. OutputDebugStringW(L"\tWITHTAG List:\n");
  1486. for (j=0; j<query->m_paTagList->GetSize(); j++)
  1487. {
  1488. swprintf (DebugStr, L"\t\t%s\n", (*query->m_paTagList)[j]);
  1489. OutputDebugStringW(DebugStr);
  1490. }
  1491. }
  1492. swprintf (DebugStr, L"\tText Offset: %d\n", query->m_ulTextOffset);
  1493. OutputDebugStringW(DebugStr);
  1494. swprintf (DebugStr, L"\tText Length: %d\n", query->m_ulTextLen);
  1495. OutputDebugStringW(DebugStr);
  1496. if ( query->m_apEntry.GetSize() )
  1497. {
  1498. for (j=0; j<query->m_apEntry.GetSize(); j++)
  1499. {
  1500. entry = query->m_apEntry[j];
  1501. if ( entry )
  1502. {
  1503. swprintf (DebugStr, L"\tDbEntry %d:\n", j+1);
  1504. OutputDebugStringW(DebugStr);
  1505. entry->GetStart(&dEntryInfo);
  1506. swprintf (DebugStr, L"\t\tFrom: %f\n", dEntryInfo);
  1507. OutputDebugStringW(DebugStr);
  1508. entry->GetEnd(&dEntryInfo);
  1509. swprintf (DebugStr, L"\t\tTo: %f\n", dEntryInfo);
  1510. OutputDebugStringW(DebugStr);
  1511. entry->GetFileName(&szEntryInfo);
  1512. OutputDebugStringW(L"\t\tFileName: ");
  1513. OutputDebugStringW(szEntryInfo);
  1514. OutputDebugStringW(L"\n");
  1515. szEntryInfo = NULL;
  1516. entry->GetText(&szEntryInfo);
  1517. OutputDebugStringW(L"\t\tText: ");
  1518. OutputDebugStringW(szEntryInfo);
  1519. OutputDebugStringW(L"\n");
  1520. szEntryInfo = NULL;
  1521. entry->GetId(&szEntryInfo);
  1522. OutputDebugStringW(L"\t\tID: ");
  1523. OutputDebugStringW(szEntryInfo);
  1524. OutputDebugStringW(L"\n");
  1525. szEntryInfo = NULL;
  1526. entry->GetStartPhone(&szEntryInfo);
  1527. if ( szEntryInfo )
  1528. {
  1529. OutputDebugStringW(L"\t\tStartPhone: ");
  1530. OutputDebugStringW(szEntryInfo);
  1531. OutputDebugStringW(L"\n");
  1532. szEntryInfo = NULL;
  1533. }
  1534. entry->GetEndPhone(&szEntryInfo);
  1535. if ( szEntryInfo )
  1536. {
  1537. OutputDebugStringW(L"\t\tEndPhone: ");
  1538. OutputDebugStringW(szEntryInfo);
  1539. OutputDebugStringW(L"\n");
  1540. szEntryInfo = NULL;
  1541. }
  1542. entry->GetLeftContext(&szEntryInfo);
  1543. if ( szEntryInfo )
  1544. {
  1545. OutputDebugStringW(L"\t\tLeftContext: ");
  1546. OutputDebugStringW(szEntryInfo);
  1547. OutputDebugStringW(L"\n");
  1548. szEntryInfo = NULL;
  1549. }
  1550. entry->GetRightContext(&szEntryInfo);
  1551. if ( szEntryInfo )
  1552. {
  1553. OutputDebugStringW(L"\t\tRightContext: ");
  1554. OutputDebugStringW(szEntryInfo);
  1555. OutputDebugStringW(L"\n");
  1556. szEntryInfo = NULL;
  1557. }
  1558. OutputDebugStringW(L"\t\tEntry Tags:\n");
  1559. entry->CountTags(&count);
  1560. for (k=0; k<count; k++)
  1561. {
  1562. entry->GetTag(&szEntryInfo, k);
  1563. swprintf (DebugStr, L"\t\t\t%s\n", szEntryInfo);
  1564. OutputDebugStringW(DebugStr);
  1565. szEntryInfo = NULL;
  1566. }
  1567. if ( query->m_afEntryMatch[j] )
  1568. {
  1569. OutputDebugStringW(L"\t\tMatch?: true\n");
  1570. }
  1571. else
  1572. {
  1573. OutputDebugStringW(L"\t\tMatch: false\n");
  1574. }
  1575. } // if ( entry )
  1576. else
  1577. {
  1578. OutputDebugStringW(L"NULL ENTRY.\n");
  1579. }
  1580. } // for
  1581. } // if
  1582. else
  1583. {
  1584. OutputDebugStringW(L"\tDbEntries: none\n");
  1585. if ( query->m_afEntryMatch.GetSize() )
  1586. {
  1587. if ( query->m_afEntryMatch[0] )
  1588. {
  1589. OutputDebugStringW(L"\tMatch: true\n");
  1590. }
  1591. else
  1592. {
  1593. OutputDebugStringW(L"\tMatch: false\n");
  1594. }
  1595. }
  1596. else
  1597. {
  1598. OutputDebugStringW(L"\tMatch: unknown\n");
  1599. }
  1600. }
  1601. OutputDebugStringW(L"END OF QUERY.\n\n");
  1602. }
  1603. swprintf ( DebugStr, L"END OF QUERY LIST. (Total Queries: %d)\n\n", m_apQueries.GetSize() );
  1604. OutputDebugStringW(DebugStr);
  1605. }