PromptManager.cpp
上传用户:xqtpzdz
上传日期:2022-05-21
资源大小:1764k
文件大小:30k
源码类别:

xml/soap/webservice

开发平台:

Visual C++

  1. /****************License************************************************
  2.  * Vocalocity OpenVXI
  3.  * Copyright (C) 2004-2005 by Vocalocity, Inc. All Rights Reserved.
  4.  * This program is free software; you can redistribute it and/or
  5.  * modify it under the terms of the GNU General Public License
  6.  * as published by the Free Software Foundation; either version 2
  7.  * of the License, or (at your option) any later version.
  8.  *  
  9.  * This program is distributed in the hope that it will be useful,
  10.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.  * GNU General Public License for more details.
  13.  *
  14.  * You should have received a copy of the GNU General Public License
  15.  * along with this program; if not, write to the Free Software
  16.  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  17.  * Vocalocity, the Vocalocity logo, and VocalOS are trademarks or 
  18.  * registered trademarks of Vocalocity, Inc. 
  19.  * OpenVXI is a trademark of Scansoft, Inc. and used under license 
  20.  * by Vocalocity.
  21.  ***********************************************************************/
  22. #include "PromptManager.hpp"
  23. #include "SimpleLogger.hpp"            // for SimpleLogger
  24. #include "VXIprompt.h"                 // for VXIpromptInterface
  25. #include "VXML.h"                      // for node names
  26. #include "DocumentModel.hpp"           // for VXMLNode, VXMLElement
  27. #include "CommonExceptions.hpp"        // for InterpreterEvent, OutOfMemory
  28. #include "PropertyList.hpp"            // for PropertyList
  29. #include <sstream>
  30. #include <iostream>
  31. static VXIchar SPACE = ' ';
  32. //#############################################################################
  33. bool static ConvertValueToString(const VXIValue * value, vxistring & source)
  34. {
  35.   if (value == NULL) return false;
  36.   switch (VXIValueGetType(value)) {
  37.   case VALUE_BOOLEAN:
  38.   {
  39.     std::basic_stringstream<VXIchar> attrStream;
  40.     if(VXIBooleanValue(reinterpret_cast<const VXIBoolean *>(value)) == TRUE)
  41.       attrStream << L"true";
  42.     else
  43.       attrStream << L"false";
  44.     source = attrStream.str();
  45.     return true;
  46.   }
  47.   case VALUE_INTEGER:
  48.   {
  49.     std::basic_stringstream<VXIchar> attrStream;
  50.     attrStream << VXIIntegerValue(reinterpret_cast<const VXIInteger *>(value));
  51.     source = attrStream.str();
  52.     return true;
  53.   }
  54.   case VALUE_FLOAT:
  55.   {
  56.     std::basic_stringstream<VXIchar> attrStream;
  57.     attrStream << VXIFloatValue(reinterpret_cast<const VXIFloat *>(value));
  58.     source = attrStream.str();
  59.     return true;
  60.   }
  61. #if (VXI_CURRENT_VERSION >= 0x00030000)
  62.   case VALUE_DOUBLE:
  63.   {
  64.     std::basic_stringstream<VXIchar> attrStream;
  65.     attrStream << VXIDoubleValue(reinterpret_cast<const VXIDouble *>(value));
  66.     source = attrStream.str();
  67.     return true;
  68.   }  
  69.   case VALUE_ULONG:
  70.   {
  71.     std::basic_stringstream<VXIchar> attrStream;
  72.     attrStream << VXIULongValue(reinterpret_cast<const VXIULong *>(value));
  73.     source = attrStream.str();
  74.     return true;
  75.   }
  76. #endif  
  77.   case VALUE_STRING:
  78.     source = VXIStringCStr(reinterpret_cast<const VXIString *>(value));
  79.     return true;
  80.   default:
  81.     return false;
  82.   }
  83. }
  84. inline void AddSSMLAttribute(const VXMLElement & elem, VXMLAttributeType attr,
  85.                              const VXIchar * const defaultVal,
  86.                              const VXIchar * const name, vxistring & sofar)
  87. {
  88.   // Get attribute or use the default value.
  89.   vxistring attrString;
  90.   if (!elem.GetAttribute(attr, attrString) || attrString.empty()) {
  91.     if (defaultVal == NULL) return;
  92.     attrString = defaultVal;
  93.   }
  94.   sofar += SPACE;
  95.   sofar += name;
  96.   sofar += L"="";
  97.   // We need to escape three characters: (",<,&) -> (&quot;, &lt;, &amp;)
  98.   vxistring::size_type pos = 0;
  99.   while ((pos = attrString.find_first_of(L""<&", pos)) != vxistring::npos) {
  100.     switch (attrString[pos]) {
  101.     case '"':   attrString.replace(pos, 1, L"&quot;");    ++pos;  break;
  102.     case '<':    attrString.replace(pos, 1, L"&lt;");      ++pos;  break;
  103.     case '&':    attrString.replace(pos, 1, L"&amp;");     ++pos;  break;
  104.     }
  105.   };
  106.   sofar += attrString;
  107.   sofar += L""";
  108. }
  109. inline void AppendTextSegment(vxistring & sofar, const vxistring & addition)
  110. {
  111.   if (addition.empty()) return;
  112.   vxistring::size_type pos = sofar.length();
  113.   sofar += addition;
  114.   // We need to escape two characters: (<,&) -> (&lt;, &amp;)
  115.   // NOTE: It starts from the old length and operates only on the addition.
  116.   while ((pos = sofar.find_first_of(L"<&", pos)) != vxistring::npos) {
  117.     switch (sofar[pos]) {
  118.     case '<':    sofar.replace(pos, 1, L"&lt;");      ++pos;  break;
  119.     case '&':    sofar.replace(pos, 1, L"&amp;");     ++pos;  break;
  120.     }
  121.   }
  122. }
  123. //#############################################################################
  124. ////////
  125. // About prompts:
  126. //
  127. // Prompts may be defined in two places within VXML documents.
  128. // 1) form items - menu, field, initial, record, transfer, subdialog, object
  129. // 2) executable content - block, catch, filled
  130. //
  131. // The <enumerate> element only is valid with parent menu or fields having one
  132. // or more option elements.  Otherwise an error.semantic results.
  133. //
  134. // From the VXML working drafts, barge-in may be controlled on a prompt by
  135. // prompt basis.  There are several unresolved issues with this request.
  136. // Instead this implements a different tactic.
  137. void PromptManager::ThrowEventIfError(VXIpromptResult rc)
  138. {
  139.   switch( rc )
  140.   {
  141.     case VXIprompt_RESULT_SUCCESS:
  142.       break;
  143.     case VXIprompt_RESULT_TTS_BAD_LANGUAGE:
  144.       throw VXIException::InterpreterEvent(EV_UNSUPPORT_LANGUAGE);
  145.     case VXIprompt_RESULT_HW_BAD_TYPE:
  146.       throw VXIException::InterpreterEvent(EV_UNSUPPORT_FORMAT);
  147.     case VXIprompt_RESULT_NO_RESOURCE:
  148.       throw VXIException::InterpreterEvent(EV_ERROR_NORESOURCE);
  149.     case VXIprompt_RESULT_NO_AUTHORIZATION:
  150.       throw VXIException::InterpreterEvent(EV_ERROR_NOAUTHORIZ);
  151.     default:  // ignore all other errors, just pass through
  152.       break;
  153.   }   
  154. }
  155. void PromptManager::WaitAndCheckError()
  156. {
  157.   VXIpromptResult rc = VXIprompt_RESULT_SUCCESS;
  158.   prompt->Wait(prompt, &rc);
  159.   ThrowEventIfError(rc);
  160. }
  161. bool PromptManager::ConnectResources(SimpleLogger * l, VXIpromptInterface * p, ExecutableContentHandler *ech)
  162. {
  163.   if (l == NULL || p == NULL || ech == NULL) return false;
  164.   log = l;
  165.   prompt = p;
  166.   contentHandler = ech;
  167.   enabledSegmentInQueue = false;
  168.   playedBargeinDisabledPrompt = false;
  169.   timeout = -1;
  170.   return true;
  171. }
  172. void PromptManager::PlayFiller(const vxistring & src, 
  173.                                const VXIMapHolder & properties)
  174. {
  175.   log->LogDiagnostic(2, L"PromptManager::PlayFiller()");
  176.   if (src.empty()) return;
  177.   const VXIMap * propMap = properties.GetValue();
  178.   // Get fetchaudiominimum value
  179.   VXIint minPlayMsec = 0;
  180.   const VXIValue * val = (VXIMapGetProperty(propMap, L"fetchaudiominimum"));
  181.   if( val &&  VXIValueGetType(val) == VALUE_STRING ) {
  182.     const VXIchar* fmin = 
  183.           VXIStringCStr(reinterpret_cast<const VXIString*>(val));
  184.     if( fmin )
  185.       if(!PropertyList::ConvertTimeToMilliseconds(*log, fmin, minPlayMsec))
  186.         throw VXIException::InterpreterEvent(EV_ERROR_SEMANTIC);
  187.   } 
  188.   prompt->PlayFiller(prompt, NULL, src.c_str(), NULL, propMap, minPlayMsec);
  189. }
  190. void PromptManager::Play(bool *playedBDPrompt)
  191. {
  192.   log->LogDiagnostic(2, L"PromptManager::Play()");
  193.   *playedBDPrompt = playedBargeinDisabledPrompt;
  194.   WaitAndCheckError();
  195.   prompt->Play(prompt);
  196.   enabledSegmentInQueue = false;
  197.   playedBargeinDisabledPrompt = false;
  198.   timeout = -1;
  199. }
  200. void PromptManager::PlayAll()
  201. {
  202.   log->LogDiagnostic(2, L"PromptManager::PlayAll()");
  203.   prompt->Play(prompt);
  204.   WaitAndCheckError();
  205.   enabledSegmentInQueue = false;
  206.   playedBargeinDisabledPrompt = false;
  207.   timeout = -1;
  208. }
  209. int PromptManager::GetMillisecondTimeout() const
  210. {
  211.   return timeout;
  212. }
  213. void PromptManager::Queue(const VXMLElement & elem, const VXMLElement & ref,
  214.                           const PropertyList & propertyList,
  215.                           PromptTranslator & translator)
  216. {
  217.   log->LogDiagnostic(2, L"PromptManager::Queue()");
  218.   // (1) Find <field> or <menu> associated with this prompt, if any.  This is
  219.   // used by the <enumerate> element.
  220.   VXMLElement item = ref;
  221.   while (item != 0) {
  222.     VXMLElementType type = item.GetName();
  223.     if (type == NODE_FIELD || type == NODE_MENU) break;
  224.     item = item.GetParent();
  225.   }
  226.   // (1.1) Search for <catch>, if found in root application
  227.   // need to replace SSML xml:base with the current one
  228.   bool replaceXmlBase = false;
  229.   VXMLElement nParent = elem.GetParent();
  230.   while( nParent != 0 ) {
  231.     if( nParent.GetName() == NODE_CATCH && 
  232.         nParent.GetDocumentLevel() == APPLICATION ) 
  233.     {
  234.       replaceXmlBase = true;
  235.       break;
  236.     }  
  237.     nParent = nParent.GetParent();
  238.   }
  239.    
  240.   // (2) Get properties.
  241.   VXIMapHolder properties;
  242.   if (properties.GetValue() == NULL) throw VXIException::OutOfMemory();
  243.   // (2.1) Flatten the property list.
  244.   propertyList.GetProperties(properties);
  245.   // (2.2) Add the URI information.
  246.   propertyList.GetFetchobjBase(properties);
  247.   // (3) Handle <prompt> elements
  248.   // (3.1) Update timeout if specified.
  249.   vxistring timeoutString;
  250.   if (elem.GetAttribute(ATTRIBUTE_TIMEOUT, timeoutString) == true)
  251.     if(!PropertyList::ConvertTimeToMilliseconds(*log, timeoutString, timeout))
  252.       throw VXIException::InterpreterEvent(EV_ERROR_SEMANTIC);
  253.   // (3.2) Get bargein setting.
  254.   PromptManager::BargeIn bargein = PromptManager::ENABLED;
  255.   const VXIchar * j = propertyList.GetProperty(PROP_BARGEIN);
  256.   if (j != NULL && vxistring(L"false") == j)
  257.     bargein = PromptManager::DISABLED;
  258.   vxistring bargeinAttr;
  259.   if (elem.GetAttribute(ATTRIBUTE_BARGEIN, bargeinAttr) == true) {
  260.     bargein = PromptManager::ENABLED;
  261.     if (bargeinAttr == L"false") bargein = PromptManager::DISABLED;
  262.     // override default bargein attribute
  263.     VXIMapSetProperty(properties.GetValue(), PROP_BARGEIN, 
  264.                      (VXIValue*)VXIStringCreate(bargeinAttr.c_str())); 
  265.   }
  266.   
  267.   vxistring bargeinType;
  268.   if (elem.GetAttribute(ATTRIBUTE_BARGEINTYPE, bargeinType) == true) {
  269.     // override default bargeintype attribute
  270.     VXIMapSetProperty(properties.GetValue(), PROP_BARGEINTYPE, 
  271.                      (VXIValue*)VXIStringCreate(bargeinType.c_str())); 
  272.   }
  273.    
  274.   // (4) Build prompt, recursively handling the contents.
  275.   vxistring content, ssmlHeader;
  276.   bool hasPromptData = false;
  277.   for (VXMLNodeIterator it(elem); it; ++it)
  278.     ProcessSegments(*it, item, propertyList, translator,
  279.                     bargein, properties, content, ssmlHeader, hasPromptData, replaceXmlBase);
  280.   if (content.empty()) return;
  281.   // (5) Add a new segment.
  282.   AddSegment(SEGMENT_SSML, content, properties, bargein);
  283. }
  284. bool PromptManager::AddSegment(PromptManager::SegmentType type,
  285.                                const vxistring & data,
  286.                                const VXIMapHolder & properties,
  287.                                PromptManager::BargeIn bargein,
  288.                                bool throwExceptions)
  289. {
  290.   const VXIMap * propMap = properties.GetValue();
  291.   VXIpromptResult result;
  292.   // Handle the easy case.
  293.   switch (type) {
  294.   case PromptManager::SEGMENT_AUDIO:
  295.     result = prompt->Queue(prompt, NULL, data.c_str(), NULL, propMap);
  296.     break;
  297.   case PromptManager::SEGMENT_SSML:
  298.     result = prompt->Queue(prompt, VXI_MIME_SSML, NULL, data.c_str(), propMap);
  299.     break;
  300.   }
  301.   if (throwExceptions) {
  302.     ThrowEventIfError(result);
  303.   }
  304.   switch (bargein) {
  305.   case PromptManager::DISABLED:
  306.     if (!enabledSegmentInQueue) {
  307.       prompt->Play(prompt);
  308.       playedBargeinDisabledPrompt = true;
  309.     }
  310.     break;
  311.   case PromptManager::ENABLED:
  312.     enabledSegmentInQueue = true;
  313.     break;
  314.   case PromptManager::UNSPECIFIED:
  315.     break;
  316.   }
  317.   return result == VXIprompt_RESULT_SUCCESS;
  318. }
  319. void PromptManager::ProcessSegments(const VXMLNode & node,
  320.                                     const VXMLElement & item,
  321.                                     const PropertyList & propertyList,
  322.                                     PromptTranslator & translator,
  323.                                     PromptManager::BargeIn bargein,
  324.                                     VXIMapHolder & props,
  325.                                     vxistring & sofar,
  326.                                     vxistring & ssmlHeader,
  327.                                     bool & hasPromptData,
  328.                                     bool replaceXmlBase)
  329. {
  330.   // (1) Handle bare content.
  331.   switch (node.GetType()) {
  332.   case VXMLNode::Type_VXMLContent:
  333.   {
  334.     const VXMLContent & content = reinterpret_cast<const VXMLContent &>(node);
  335.     const vxistring & data = content.GetValue();
  336.     if( sofar.empty() && ssmlHeader.empty() )
  337.       ssmlHeader = data.substr(0, content.GetSSMLHeaderLen());      
  338.     // If the content contains only </speak> and there is no audio 
  339.     // the prompt is empty, no need to queue empty prompt!!!
  340.     if( data == L"</speak>" && sofar.length() <= ssmlHeader.length() )
  341.       sofar = L"";
  342.     else {
  343.       bool done = false;          
  344.       const VXIchar* baseuri = propertyList.GetProperty(PropertyList::BaseURI);
  345.       if( replaceXmlBase && baseuri && sofar.empty() && !ssmlHeader.empty()) {
  346.         // In case of the prompt being played from the <catch> element which 
  347.         // originated from the root application.  According to VoiceXML 2.0
  348.         // specs. All relative uri must be resolved to the active document due
  349.         // to the as-if-by-copy semantic
  350.         vxistring::size_type idx = ssmlHeader.find(L"xml:base");
  351.         vxistring::size_type idxDelim1, idxDelim2;
  352.         if( *baseuri != L'' && idx != vxistring::npos ) {
  353.           // Found the xml:base attribute
  354.           // Now search for delimiter "
  355.           idxDelim1 = ssmlHeader.find(L""", idx);
  356.           if( idxDelim1 != vxistring::npos )
  357.           {
  358.             // Found delimiter "
  359.             idxDelim2 = ssmlHeader.find(L""", idxDelim1 + 1);              
  360.           }
  361.           else {
  362.             // Try to find delimiter '
  363.             idxDelim1 = ssmlHeader.find(L"'", idx);
  364.             idxDelim2 = ssmlHeader.find(L"'", idxDelim1 + 1);                        
  365.           }
  366.           // Replace base uri
  367.           if( idxDelim1 != vxistring::npos && 
  368.               idxDelim2 != vxistring::npos ) {
  369.             idxDelim1++;
  370.             vxistring buri = baseuri;
  371.             idx = buri.find_first_of(L"?");
  372.             if( idx != vxistring::npos ) buri = buri.substr(0,idx);
  373.             ssmlHeader.replace(idxDelim1, idxDelim2-idxDelim1, buri);
  374.             sofar += ssmlHeader;
  375.             sofar += data.substr(content.GetSSMLHeaderLen());
  376.             done = true;
  377.           }
  378.         }
  379.       }
  380.       // pass-through
  381.       if( !done ) {
  382.         sofar += data;
  383.       }
  384.     }
  385.     hasPromptData = (sofar.length() > ssmlHeader.length());
  386.     return;
  387.   }
  388.   case VXMLNode::Type_VXMLElement:
  389.     break;
  390.   default: // This should never happen.
  391.     log->LogError(999, SimpleLogger::MESSAGE,
  392.                   L"unexpected type in PromptManager::ProcessSegments");
  393.     throw VXIException::Fatal();
  394.   }
  395.   // (2) Retrieve fetch properties (if we haven't already)
  396.   const VXMLElement & elem = reinterpret_cast<const VXMLElement &>(node);
  397.   vxistring attr;
  398.   switch (elem.GetName()) {
  399.   // (3) audio
  400.   case NODE_AUDIO:
  401.   {
  402.     // (3.1) Process the 'expr' if provided.  NOTE: Exactly one of 'src' and 
  403.     //       'expr' MUST be specified.
  404.     vxistring expr;
  405.     
  406.     if (elem.GetAttribute(ATTRIBUTE_EXPR, expr)) {
  407.       // (3.1.1) Handle the expr case.
  408.       VXIValue * value = translator.EvaluateExpression(expr);
  409.       if (value == NULL) return;  // Ignore this element
  410.   
  411.       // (3.1.2) Handle audio content from <record> elements.
  412.       if (VXIValueGetType(value) == VALUE_CONTENT) {
  413.         // Queue the current prompt segment
  414.         if( hasPromptData )
  415.         {
  416.           sofar += L"</speak>";
  417.           AddSegment(SEGMENT_SSML, sofar, props, bargein);
  418.         }
  419.         // reset 
  420.         sofar = ssmlHeader;
  421.         AddContent(props, sofar, value);
  422.         // Referencing recorded data by queuing mark name
  423.         sofar += L"</speak>";
  424.         AddSegment(SEGMENT_SSML, sofar, props, bargein);
  425.         // reset
  426.         sofar = ssmlHeader;        
  427.         return;
  428.       }
  429.       // (3.1.3) Otherwise, try to convert the type into a string.
  430.       bool conversionFailed = !ConvertValueToString(value, expr);
  431.       VXIValueDestroy(&value);
  432.   
  433.       if (conversionFailed) {
  434.         log->LogDiagnostic(0, L"PromptManager::ProcessSegments - "
  435.                            L"audio src evaluation failed");
  436.         throw VXIException::InterpreterEvent(EV_ERROR_SEMANTIC,
  437.                                              L"audio src evaluation failed");
  438.       }
  439.       if (expr.empty()) return;  // Ignore this element
  440.     }
  441.     // (3.2) Add an <audio> element to the current request.
  442.     sofar += L"<audio";
  443.     AddSSMLAttribute(elem, ATTRIBUTE_SRC, expr.c_str(), L"src", sofar);
  444.     AddSSMLAttribute(elem, ATTRIBUTE_FETCHTIMEOUT,
  445.                      propertyList.GetProperty(L"fetchtimeout"),
  446.                      L"fetchtimeout", sofar);
  447.     AddSSMLAttribute(elem, ATTRIBUTE_FETCHHINT,
  448.                      propertyList.GetProperty(L"audiofetchhint"),
  449.                      L"fetchhint", sofar);
  450.     AddSSMLAttribute(elem, ATTRIBUTE_MAXAGE,
  451.                      propertyList.GetProperty(L"audiomaxage"),
  452.                      L"maxage", sofar);
  453.     AddSSMLAttribute(elem, ATTRIBUTE_MAXSTALE,
  454.                      propertyList.GetProperty(L"audiomaxstale"),
  455.                      L"maxstale", sofar);
  456.     // Add in the alternate text (if any).
  457.     if (elem.hasChildren()) {
  458.       sofar += L">";
  459.       for (VXMLNodeIterator it(elem); it; ++it)
  460.         ProcessSegments(*it, item, propertyList, translator,
  461.                         bargein, props, sofar, ssmlHeader, hasPromptData, replaceXmlBase);
  462.       sofar += L"</audio>";
  463.     }
  464.     else
  465.       sofar += L"/>";
  466.     return;
  467.   }
  468.   // (4) <value>
  469.   case NODE_VALUE:
  470.   {
  471.     // (4.1) Evaluate the expression.  Can we handle this type?
  472.     vxistring expr;
  473.     if (!elem.GetAttribute(ATTRIBUTE_EXPR, expr) || expr.empty()) return;
  474.     VXIValue * value = translator.EvaluateExpression(expr);
  475.     if (value == NULL) return;
  476.     // (4.2) Convert to a string a play as TTS.
  477.     bool conversionFailed = !ConvertValueToString(value, expr);
  478.     VXIValueDestroy(&value);
  479.     if (conversionFailed) {
  480.       log->LogDiagnostic(0, L"PromptManager::ProcessSegments - "
  481.                          L"value expr was not a simple type");
  482.       throw VXIException::InterpreterEvent(EV_ERROR_SEMANTIC,
  483.                                            L"value expr gave invalid type");
  484.     }
  485.     if (expr.empty()) return;
  486.     AppendTextSegment(sofar, expr);
  487.     return;
  488.   }
  489.   break;
  490.   // (5) <enumerate>
  491.   case NODE_ENUMERATE:
  492.   {
  493.     // (5.1) Is enumerate valid in this location?
  494.     if (item == 0)
  495.       throw VXIException::InterpreterEvent(EV_ERROR_SEMANTIC, L"invalid use "
  496.                                            L"of enumerate element");
  497.     bool foundMatch = false;
  498.     for (VXMLNodeIterator it(item); it; ++it) {
  499.       // (5.2) Ignore anything which isn't an <option> or <choice> grammar.
  500.       if ((*it).GetType() != VXMLNode::Type_VXMLElement) continue;
  501.       const VXMLNode & tmp = *it;
  502.       const VXMLElement & itemElem = reinterpret_cast<const VXMLElement&>(tmp);
  503.       switch (itemElem.GetName()) {
  504.       case NODE_CHOICE:
  505.       case NODE_OPTION:
  506.         foundMatch = true;
  507.         break;
  508.       default:
  509.         continue;
  510.       }
  511.       // (5.3) Convert item into text.
  512.       vxistring promptText;
  513.       for (VXMLNodeIterator choiceNode(itemElem); choiceNode; ++choiceNode) {
  514.         if ((*choiceNode).GetType() == VXMLNode::Type_VXMLContent) {
  515.           if (!promptText.empty()) promptText += SPACE;
  516.           const VXMLNode temp = *choiceNode;
  517.   
  518.           promptText += reinterpret_cast<const VXMLContent &>(temp).GetValue();
  519.           /*
  520.           // Get text from this node.
  521.           vxistring text = reinterpret_cast<const VXMLContent &>
  522.                              (*choiceNode).GetValue();
  523.           if (!promptText.empty()) promptText += SPACE;
  524.           // Strip the leading whitespace.
  525.           while (text.length() > 0) {
  526.             VXIchar c = text[0];
  527.             if (c == ' ' || c == 'r' || c == 'n' || c == 't')
  528.               text.erase(0, 1);
  529.             else {
  530.               promptText += text;
  531.               break;
  532.             }
  533.           }
  534.           // Strip off any trailing whitespace
  535.           while (promptText.length() > 0) {
  536.             unsigned int len = promptText.length();
  537.             VXIchar c = promptText[len - 1];
  538.             if (c == ' ' || c == 'r' || c == 'n' || c == 't')
  539.               promptText.erase(len - 1, 1);
  540.             else break;
  541.           }
  542.           */
  543.         }
  544.       }
  545.       // (5.4) Handle the simple case where enumerate does not specify a
  546.       // format string.
  547.       if (!elem.hasChildren()) {
  548.         AppendTextSegment(sofar, promptText);
  549.         sofar += L"<break time='small'/>";
  550.       }
  551.       else {
  552.         // (5.5) Get the associated dtmf value.
  553.         vxistring dtmfText;
  554.         itemElem.GetAttribute(ATTRIBUTE_DTMF, dtmfText);
  555.         translator.SetVariable(L"_prompt", promptText);
  556.         translator.SetVariable(L"_dtmf",   dtmfText);
  557.         for (VXMLNodeIterator subNode(elem); subNode; ++subNode)
  558.           ProcessSegments(*subNode, item, propertyList, translator,
  559.                           bargein, props, sofar, ssmlHeader, hasPromptData, replaceXmlBase);
  560.       }
  561.     }
  562.     // (5.6) Final check - was there an <option> or <choice>?
  563.     if (!foundMatch)
  564.       throw VXIException::InterpreterEvent(EV_ERROR_SEMANTIC, L"invalid use "
  565.                                            L"of enumerate element");
  566.     break;
  567.   }
  568.   // (6) <foreach>
  569.   case NODE_FOREACH:
  570.   {
  571.     log->LogDiagnostic(2, L"PromptManager::ProcessSegments(), Handle foreach");
  572.     // (6.1) Get array attribute, and verify that it's an Array
  573.     vxistring array;
  574.     elem.GetAttribute(ATTRIBUTE_ARRAY, array);
  575.     vxistring test = array + L" instanceof Array";
  576.     if (!translator.TestCondition(test)) {
  577.       vxistring msg = array + L" is not an array";
  578.       throw VXIException::InterpreterEvent(EV_ERROR_SEMANTIC, msg);
  579.     }
  580.     // (6.2) Get item
  581.     vxistring itemName;
  582.     elem.GetAttribute(ATTRIBUTE_ITEM, itemName);
  583.     translator.EvalScript( L"var " + itemName + L" = null;" );
  584.     // (6.3) Iterate array
  585.     VXIValue *vlen = translator.EvaluateExpression( array + L".length" );
  586.     VXIunsigned len = VXIIntegerValue(reinterpret_cast<VXIInteger *>(vlen));
  587.     for (int i = 0; i < len; i++) {
  588.       // (6.3.1) Set the item with the current array element
  589.       std::basic_stringstream<VXIchar> script;
  590.       script << itemName << L" = " << array << L"[" << i << L"];";
  591.   translator.EvalScript(script.str());
  592.       // (6.3.2) Process child elements of the <foreach>
  593.       for (VXMLNodeIterator it(elem); it; ++it)
  594.         ProcessSegments(*it, item, propertyList, translator,
  595.                         bargein, props, sofar, ssmlHeader, hasPromptData, replaceXmlBase);
  596.     }
  597.     break;
  598.   }
  599.   // (7) <mark>
  600.   case NODE_MARK:
  601.   {
  602.     // (7.1) expand "nameexpr" if needed
  603.     vxistring name;
  604.     elem.GetAttribute(ATTRIBUTE_NAME, name);
  605.     if (name.empty()) {
  606.       elem.GetAttribute(ATTRIBUTE_NAMEEXPR, name);
  607.       if(name.empty()) return;
  608.       VXIValue * value = translator.EvaluateExpression(name);
  609.       if (value == NULL) return;
  610.       if(!ConvertValueToString(value, name)) name.clear();
  611.       VXIValueDestroy(&value);
  612.     }
  613.     if(!name.empty())
  614.       sofar += L"<mark name='" + name + L"'/>";
  615.     break;
  616.   }
  617.   // Executable content
  618.   default:
  619.     contentHandler->executable_element(elem);
  620.   }
  621. }
  622. void PromptManager::AddContent(VXIMapHolder & props,
  623.                                vxistring & sofar,
  624.                                VXIValue * value)
  625. {
  626.   VXIMapHolder allRefs(NULL);
  627.   // (1) Get existing map (if it exists) or create a new one.
  628.   const VXIValue * temp = VXIMapGetProperty(props.GetValue(),
  629.                                             PROMPT_AUDIO_REFS);
  630.   if (temp != NULL && VXIValueGetType(temp) == VALUE_MAP)
  631.     allRefs.Acquire(VXIMapClone(reinterpret_cast<const VXIMap *>(temp)));
  632.   else
  633.     allRefs.Acquire(VXIMapCreate());
  634.   if (allRefs.GetValue() == NULL) throw VXIException::OutOfMemory();
  635.   // (2) Generate a hidden name.
  636.   vxistring hiddenName;
  637.   VXMLDocumentModel::CreateHiddenVariable(hiddenName);
  638.   hiddenName.erase(0, 1);
  639.   hiddenName = vxistring(PROMPT_AUDIO_REFS_SCHEME) + hiddenName;
  640.   // (3) Place hidden name and content into the map.
  641.   VXIMapSetProperty(allRefs.GetValue(), hiddenName.c_str(), value);
  642.   // (4) Replace the references map with the updated one.
  643.   VXIMapSetProperty(props.GetValue(), PROMPT_AUDIO_REFS,
  644.                     reinterpret_cast<VXIValue *>(allRefs.Release()));
  645.   
  646.   // (5) Handle the SSML case
  647.   sofar += L"<mark name="";
  648.   sofar += hiddenName;
  649.   sofar += L""/>";
  650. }
  651. bool PromptManager::ProcessPrefetchPrompts(const VXMLNode & node, const VXMLElement& item,
  652.                       const PropertyList & propertyList, 
  653.                       PromptTranslator & translator, 
  654.                       vxistring & sofar)
  655. {
  656.   bool rc = false;
  657.    
  658.   switch (node.GetType()) {
  659.     case VXMLNode::Type_VXMLContent:
  660.     {
  661.       const VXMLContent & content = reinterpret_cast<const VXMLContent &>(node);
  662.       sofar += content.GetValue();
  663.       return true;
  664.     }
  665.     case VXMLNode::Type_VXMLElement:
  666.       break;
  667.     default:
  668.       return false;
  669.   }
  670.         
  671.   // Look at other type
  672.   const VXMLElement & elem = reinterpret_cast<const VXMLElement &>(node);
  673.   switch (elem.GetName()) 
  674.   {
  675.     // audio
  676.     case NODE_AUDIO:
  677.     {
  678.       // ignore this entire prompt if expr is found
  679.       vxistring expr;
  680.       if (elem.GetAttribute(ATTRIBUTE_EXPR, expr) == true)
  681.         return false;
  682.       else {
  683.         rc = true;
  684.         sofar += L"<audio";
  685.         AddSSMLAttribute(elem, ATTRIBUTE_SRC, expr.c_str(), L"src", sofar);
  686.         
  687.         AddSSMLAttribute(elem, ATTRIBUTE_FETCHTIMEOUT,
  688.                      propertyList.GetProperty(L"fetchtimeout"),
  689.                      L"fetchtimeout", sofar);
  690.         AddSSMLAttribute(elem, ATTRIBUTE_FETCHHINT,
  691.                      propertyList.GetProperty(L"audiofetchhint"),
  692.                      L"fetchhint", sofar);
  693.         AddSSMLAttribute(elem, ATTRIBUTE_MAXAGE,
  694.                      propertyList.GetProperty(L"audiomaxage"),
  695.                      L"maxage", sofar);
  696.         AddSSMLAttribute(elem, ATTRIBUTE_MAXSTALE,
  697.                      propertyList.GetProperty(L"audiomaxstale"),
  698.                      L"maxstale", sofar);
  699.                          
  700.         // Add in the alternate text (if any).
  701.         if (elem.hasChildren()) {
  702.           sofar += L">";
  703.           for (VXMLNodeIterator it(elem); rc && it; ++it)
  704.             rc = ProcessPrefetchPrompts(*it, item, propertyList, translator, sofar);         
  705.           sofar += L"</audio>";
  706.         }
  707.         else
  708.           sofar += L"/>";      
  709.         
  710.         if( rc ) sofar += L"</speak>";
  711.       }
  712.     
  713.     } break;    
  714.     
  715.     // value tag and other tags found inside a prompt, do not fetch this entire prompt
  716.     // case NODE_VALUE: case NODE_ENUMERATE:
  717.     default:
  718.       return false;
  719.   }
  720.   return rc;
  721. }
  722. void PromptManager::PreloadPrompts(const VXMLElement& doc,
  723.                                    PropertyList & properties, 
  724.                                    PromptTranslator & translator)
  725. {
  726.   VXIMapHolder temp(NULL);
  727.   DoPreloadPrompts(doc, properties, temp, translator);
  728. }
  729. void PromptManager::DoPreloadPrompts(const VXMLElement& doc,
  730.                                      PropertyList & properties,
  731.                                      VXIMapHolder & levelProperties,
  732.                                      PromptTranslator & translator)
  733. {
  734.   // (1) Look for prompts in current nodes.
  735.   for (VXMLNodeIterator it(doc); it; ++it) {
  736.     VXMLNode child = *it;
  737.     if (child.GetType() != VXMLNode::Type_VXMLElement) continue;
  738.     const VXMLElement & element = reinterpret_cast<const VXMLElement &>(child);
  739.     VXMLElementType elementName = element.GetName();
  740.     // (2) Handle anything which is not a prompt.
  741.     if (elementName != NODE_PROMPT) { 
  742.       bool doPop = properties.PushProperties(element);
  743.       if (doPop) {
  744.         VXIMapHolder temp(NULL);
  745.         DoPreloadPrompts(element, properties, temp, translator);
  746.       }
  747.       else
  748.         DoPreloadPrompts(element, properties, levelProperties, translator);
  749.       if (doPop) properties.PopProperties();
  750.       continue;
  751.     }
  752.     // (3) Handle <prompt>.  
  753.     // (3.1) Ignore anything other than simple content.
  754.     if (!element.hasChildren()) continue;
  755.     // (3.2) Find the fetchhint setting.
  756.     if (levelProperties.GetValue() == NULL) {
  757.       levelProperties.Acquire(VXIMapCreate());
  758.       properties.GetProperties(levelProperties);
  759.     }
  760.     vxistring attr = L"";
  761.     if (element.GetAttribute(ATTRIBUTE_FETCHHINT, attr) != true) {
  762.       const VXIValue * v = VXIMapGetProperty(levelProperties.GetValue(),
  763.                                              L"audiofetchhint");
  764.       if (VXIValueGetType(v) == VALUE_STRING)
  765.         attr = VXIStringCStr(reinterpret_cast<const VXIString *>(v));
  766.     }
  767.     // (3.3) Get the level properties if necessary & add the prefetch setting.
  768.     if (attr == L"prefetch")
  769.       AddParamValue(levelProperties, PROMPT_PREFETCH_REQUEST, 1);
  770.     else
  771.       AddParamValue(levelProperties, PROMPT_PREFETCH_REQUEST, 0);
  772.     // (3.4) Create full ssml document
  773.     vxistring ssmldoc;    
  774.     bool shouldPrefetch = true;
  775.     
  776.     for (VXMLNodeIterator it2(element); shouldPrefetch && it2; ++it2)
  777.        shouldPrefetch = ProcessPrefetchPrompts(*it2, element, properties, translator, ssmldoc);     
  778.      // (3.5) Call prefetch for prompt.
  779.     if( shouldPrefetch ) {
  780.       prompt->Prefetch(prompt, VXI_MIME_SSML, NULL,
  781.                      ssmldoc.c_str(),
  782.                      levelProperties.GetValue());
  783.     }
  784.     
  785.     // (3.6) We might undo the addition of PROMPT_PREFETCH_REQUEST, but this
  786.     //       parameter is always set and will be overwritten on the next pass.
  787.   }
  788. }