Generating VoiceXML Content

You can build powerful voice applications using VoiceXML and JavaScript. These languages are executed by the VoiceXML interpreter to handle call flow logic and user interaction. To tie your phone application into back-end systems such as databases and middle-tier business logic, your voice application needs some way to interact with those systems. To do so, you have a couple of options: generate VoiceXML on the fly, or reimplement the business logic within your voice application. The former is preferred since maintaining multiple sets of business logic requires duplication of effort that is error-prone and a waste of resources.

Generating VoiceXML content dynamically from server-side scripts leverages the power and flexibility of the application server technologies provided by popular Web servers today. This example demonstrates submitting data to and receiving a VoiceXML response from an Active Server Pages (ASP) and a CGI script.

Try It!
  1. Set your Application URL to http://studio.247-inc.net/library2/code/ex-105/index.vxml
  2. Call the number shown in VXML Tools page
  3. Sign in, and play!

How it works

This example demonstrates how to incorporate dynamic content by calling a CGI script written in Perl. Web developers familiar with generating HTML pages from swith CGI or ASP exerience will be familiar with how this works.

The VoiceXML application first gathers the user's birthday (month and day) in two separate dialogs. It uses knowledge of the month and the number of days in each month to perform some validation in the second dialog. If, for example, the user says "April" in response to the question "In which month were you born?", and the user then says "31" in response to the question "On which day in April were you born?", the voice application rejects the response and requests a different day.

Once a valid day and month have been collected, the application calls an external script using the submit element. The purpose of the script is to calculate the number of days until the user's birthday. To do so, the server-side script processes the input parameters it receives and generates VoiceXML content dynamically, sending it back to the calling VoiceXML interpreter. The VoiceXML interpreter subsequently executes the dynamically generated content in the same way that it executes static content.

This example takes input using two form elements. It then passes the caller's input to an external CGI script that calculates the number of days remaining until the caller's birthday.

VoiceXML 2.0 Code

Here is the VoiceXML code for this example

<!--
Tellme Studio Address Capture Example
Copyright (C) 2002-2003 Tellme Networks, Inc. All Rights Reserved.

THIS CODE IS MADE AVAILABLE SOLELY ON AN "AS IS" BASIS, WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
WARRANTIES THAT THE CODE IS FREE OF DEFECTS, MERCHANTABLE, 
FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
-->
<vxml version="2.0" 
  xmlns="http://www.w3.org/2001/vxml">

   <link event="help">
   
     <grammar mode="voice"
              root="root_rule"
              tag-format="semantics/1.0"
              type="application/srgs+xml"
              version="1.0"
              xml:lang="en-US">
       <rule id="root_rule" scope="public">
         <one-of>
           <item>
             help
           </item>
         </one-of>
       </rule>
     </grammar>

   </link>


   <link event="event.ongoback">
   
     <grammar mode="voice"
              root="root_rule"
              tag-format="semantics/1.0"
              type="application/srgs+xml"
              version="1.0"
              xml:lang="en-US">
       <rule id="root_rule" scope="public">
         <one-of>
           <item>
             <item repeat="0-1">
               go
             </item>
             back
          </item>
        </one-of>
      </rule>
    </grammar>

   </link>

   <script>
   // simple state transition manager
   var hStates = {"#get_month" : {"next" : "#get_day", "previous" : "#get_month"},
                 "#get_day" : {"next" : "#call_cgi", "previous" : "#get_month"}, 
                 "#call_cgi" : {"next" : "#done", "previous" : "#get_day"}};

   // which dialog are we in?
   var current_state = "#get_month";
  </script>

   <catch event="event.ongoback">
      <goto expr="hStates[current_state].previous"/>
   </catch>

   <catch event="event.onnextstate">
      <goto expr="hStates[current_state].next"/>
   </catch>

   <!-- Store data at document scope to share it between dialogs  -->
   <var name="month"/>
   <var name="aMonthNames" expr="new Array('january', 'february', 'march', 
                      'april', 'may', 'june', 'july', 'august', 'september', 'october', 
                      'november', 'december')"/>
   <var name="day"/>

   <!--*************************************************************
   get_month state: asks for the caller's month of birth using the intrinsic
   MONTH grammar. The grammar returns the month name in string form (e.g.
   "january", "february").
   *************************************************************-->
   <form id="get_month">

   <block>
         <assign name="current_state" expr="'#get_month'"/>
   </block>
      
   <field name="month">

      <grammar type="application/srgs+xml" mode="voice" 
        src="../../grammar/ex/month.grxml"/>
      <grammar type="application/srgs+xml" mode="dtmf" 
        src="../../grammar/ex/month-dtmf.grxml"/>

      <prompt>
         <audio>In which month were you born</audio>
      </prompt>
      
      <noinput>
         <audio> I'm sorry. i didn't hear you </audio>
         <reprompt/>
      </noinput>

      <nomatch>
         <audio> I'm sorry. i didn't get that  </audio>
         <reprompt/>
      </nomatch>

      <help>
      Say the name of a month. Or, use your touch-tone key pad to indicate the month.
      For example, for January, press "1". For June, press "6". For December, press "12".
      </help>
      
      <filled>
         <audio>i heard you say <value expr="aMonthNames[month]"/></audio>
         <assign name="document.month" expr="month"/>
         <throw event="event.onnextstate"/>
      </filled>
   </field>
   </form>

   <!--*************************************************************
   get_day state: asks for the caller's day of birth using the day
   grammar. The grammar returns a number in string form (e.g.
   "1", "2", ... "31")
   *************************************************************-->
   <form id="get_day">   

   <!-- aMaxDays allows us to do some "client-side" validation before generating network traffic
                                          fe      ap     ju         se     no
 -->                                             
   <var name="aMaxDays" expr="new Array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)"/>   

   <block>
         <assign name="current_state" expr="'#get_day'"/>
   </block>

   <field name="day">
      <grammar type="application/srgs+xml" mode="voice" 
        src="../../grammar/ex/monthday.grxml"/>
      <grammar type="application/srgs+xml" mode="dtmf" 
        src="../../grammar/ex/monthday-dtmf.grxml"/>

      <prompt>
         <audio>On which day in <value expr="aMonthNames[month]"/> were you born?</audio>
         <break time="300ms"/>
         <audio>Say go back to choose a different month</audio>
      </prompt>

      <noinput>
         <audio> I'm sorry. i didn't hear you </audio>
         <reprompt/>
      </noinput>
      
      <nomatch>
         <audio> I'm sorry. i didn't get that  </audio>
         <reprompt/>
      </nomatch>

      <help>
         Say the day in <value expr="aMonthNames[month]"/> that you were born.
      </help>
            
      <filled>
         <log>specified day= <value expr="day"/>, max days = <value expr="aMaxDays[month]"/></log>
         <if cond="day &gt; aMaxDays[month]">
            <audio>i heard you say <value expr="day"/>, but I must have misheard you
               because <value expr="aMonthNames[month]"/> has only <value expr="aMaxDays[month]"/> days             
            </audio>
            <!-- add a little more audio in the feb case -->
            <if cond="month==1">
               <audio> in a leap year</audio>
            </if>
            <clear namelist="day"/>
            <reprompt/>
         <else/>
            <audio>i heard you say <value expr="day"/></audio>
            <assign name="document.day" expr="day"/>
            <throw event="event.onnextstate"/>
         </if>
      </filled>
   </field>
   </form>
   

   <!--*************************************************************
   There are two ways to pass parameters to a CGI script: HTTP POST
   and HTTP GET (also known as the URL querystring). 

   If the parameters accepted by the CGI script match the variable
   names in your VoiceXML code, use the namelist attribute of the 
   submit element to pass the data to the CGI. If the names don't match,
   you can assign the values to temporary variables with the correct names.
   If you're using HTTP GET and the names don't match, you can also 
   construct the querystring manually and use the expr attribute of the 
   submit element.
   
   URLs with querystrings tyically look something like this:
   http://webserver/script.pl?input1=value1&input2=value2&input3=value3

   Note that this example first constructs the entire querystring and stores
   the string into the "sQuery" application variable. 
   The application variable is in turn passed to the CGI script.
   *************************************************************-->
   
   <form id="call_cgi">

   <block>
         <assign name="current_state" expr="'#call_cgi'"/>
   </block>

   <field name="mode">
   
   <prompt>
      <audio>To submit the birthday data to a perl c g i using namelist, press 1?</audio>
      <audio>To construct the query string manually and submit the birthday data to a perl c g i , press 2?</audio>
   </prompt>
   
   
   <grammar mode="dtmf"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0">
      <rule id="root_rule" scope="public">
         <one-of>
            <item>
               <one-of>
                  <item>
                     1
                  </item>
               </one-of>
               <tag>out.mode = "cgi-auto";</tag>
            </item>
            <item>
               <one-of>
                  <item>
                     2
                  </item>
               </one-of>
               <tag>out.mode = "cgi-manual";</tag>
            </item>
         </one-of>
      </rule>

   </grammar>


   <noinput>
      <!-- default and move on to block -->
      <assign name="mode" expr="'cgi-auto'"/>
   </noinput>

   <nomatch>
      <audio>Sorry. I didn't get that.</audio>
      <reprompt/>
   </nomatch>

   <help>
   Press 1 or 2 to find out how many days until your next birthday.
   </help>

   <catch event="nomatch" count="3">
      <exit/>
   </catch>

   <filled>
      <!-- that's nice -->
   </filled>
   </field>

   <block>
      <!-- Calculate number of days until user's birthday on server-side -->
      <if cond="mode=='cgi-auto'">
         <!-- Use namelist and let the VoiceXML interpreter do the work of constructing the querystring -->
         <submit next="ex-105a.cgi" namelist="month day"/>
      <elseif cond="mode=='cgi-manual'"/>
         <!-- This version constructs the query string manually and uses the expr attribute: -->
         <var name="sQuery" expr="'ex-105a.cgi?month=' + month + '&amp;day=' + day"/> 
         <submit expr="sQuery" />      
      <else />
         <audio><value expr="mode"/> is an unsupported mode.</audio>
         <log>Unsupported mode : <value expr="mode"/></log>
         <goto next="#get_month"/>
      </if>
   </block>
   </form>   
</vxml>

PERL CGI

Here is the server-side script authored in Perl:

#!/usr/local/bin/perl -w

# Tellme Studio Code Example 105
# Copyright (C) 2000-2001 Tellme Networks, Inc. All Rights Reserved.

#THIS CODE IS MADE AVAILABLE SOLELY ON AN "AS IS" BASIS, WITHOUT WARRANTY
#OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
#WARRANTIES THAT THE CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A
#PARTICULAR PURPOSE OR NON-INFRINGING.

use Time::Local;
use CGI qw/:standard/;
use strict;

my $today_is_bday = 0;
my $query = new CGI;

# grab the 0-based month and 1-based day
my $month_in = $query->param('month');
my $day_in   = $query->param('day');

my $yday_now = 0;
my $year_now = 0;

my $yday_bday = 0;
my $days_til_bday = 0;

($_,$_,$_,$_,$_,$year_now,$_,$yday_now,$_) = localtime;

my $bday_time = timelocal(0,0,0,$day_in,$month_in,$_);

($_,$_,$_,$_,$_,$_,$_,$yday_bday,$_) = localtime($bday_time);

if ($yday_bday == $yday_now) {
  $today_is_bday = 1;
} else {
  if ($yday_bday > $yday_now) {
    $days_til_bday = $yday_bday - $yday_now;
  } else {
    $days_til_bday = $yday_bday - $yday_now + 365;
  }
}


print "Content-type: text/xml\n\n";
print "<?xml version=\"1.0\"?>\n";
print "<vxml version=\"2.0\">\n";
print "<form id=\"result\"><block>\n";
print "<audio>\n";
if ($today_is_bday) {
  print "happy birthday!\n";
} else {
  print "your birthday is $days_til_bday days away\n";
}
print "</audio>\n";
print "</block></form></vxml>\n";

Active Server Page

Here is the server-side script authored using Microsoft's Active Server Pages:

<%

'declare variables
dim month_in, day_in, days_to_bDate

'grab params from Request object
month_in = Request("month")
day_in = Request("day")

'create birthdate in date format by concatenating month_in and day_in
bDate = month_in & " " & day_in

'use intrinsic VBScript function to find out the number of days til users birthday
'see http://msdn.microsoft.com/scripting/default.htm?/scripting/vbscript/doc/vsfctDateDiff.htm
days_to_bDate = DateDiff("d", Now, bDate)

'DateDiff returns a negative number if date happened in the past.  Add 365 days to calculate days in the future.
If days_to_bDate < 0 Then
   days_to_bDate = days_to_bDate + 365
End If

Response.ContentType = "text/xml"

'Write VoiceXML response.
%>
<vxml version="2.0">
   <form id="result"><block>
   <audio>
   <%
      If days_to_bDate = 0 Then
   %>
      happy birthday!
   <%
   Else
   %>
      your birthday is <% =days_to_bDate %> days away
   <%
   End If
   %>
   </audio>
   <exit/>
   </block></form>
</vxml>