VoiceXML 2.1 Development GuideHome  |  Frameset Home

  tutorial XML Grammars  |  TOC  |  tutorial Using the Mark Element  

VoiceXML Tutorial: Using <foreach>, <data>, and DOM

Our tutorial will explain, step by step, how to develop a basketball news grabber for Voxeo's VoiceXML 2.1 browser. This static application fetches data from an RSS newsfeed from NBA.com and, through voice recognition commands, allows callers to get real-time news on the team of their choice.

We're going to use the <data> element to fetch our newsfeed, a JavaScript function to format the returned data, and the <foreach> element to loop through the data and present it to the caller. We're also going to use the <link> tag for global user commands and show how to use document-scoped variables in the VoiceXML context.

Most of us have written some VoiceXML applications by now, so our starting framework should be recognizable to most of us. If you are a newcomer to VoiceXMXL, we recommend trying at least a few of our "beginner" lessons so that you won't be left behind. This tutorial is geared toward the developer who is confident with using basic VoiceXML syntax and has some familiarity with JavaScript.


Step 1: write the main VoiceXML file

Our XML dialog is going to have three distinct steps, so we will create three different <form> tags to encapsulate these steps:


To start off, we will lay out our basic document structure, prompt listing, and navigation logic. We will also declare some variables at the document-scope to hold our user input, to make sure that it will scope across the different <forms>. In the  interest of brevity, we will include some inline notations to explain their use in the application:



<?xml version="1.0" encoding="UTF-8"?>
<vxml version="2.1" xmlns:voxeo="http://community.voxeo.com/xmlns/vxml">

<!-- DECLARE DOCUMENT SCOPED VARIABLES -->

  <!-- THIS VARIABLE WILL HOLD THE CALLER'S TEAM CHOICE -->
  <var name="teamRSS"/>
 
  <var name="dataRSS"/>
  <!-- THIS HOLDS THE NODE REFERENCE FOR OUR DATA WE FETCH FROM THE RSS -->
  <var name="descriptionArrayRSS"/>
  <!-- THIS VARIABLE WILL BE POPULATED WITH AN ARRAY CONTAINING ALL ITEMS FROM THE RSS FEED-->
  <var name="fetchRSS"/>
  <!-- THIS IS OUR DATA ELEMENT'S FORM ITEM VARIABLE-->
  <var name="resultCount"/>

<form id="Intro">

  <block>
    <prompt bargein="true">
    <audio src="welcome.wav">
      Welcome to the real time Basketball news, where you can get up to date
      information on the recent happenings for your favorite En Bee Eh team.
    </audio>
    </prompt>
    <goto next="#Team"/>
  </block>
</form>


<form id="Team">
  <field name="chooseTeam" modal="true">
    <grammar src="TeamGram.php" type="application/srgs+xml"/>
    <prompt baregin="true">
    <audio src="chooseTeam.wav">
      To begin, please choose the name of the En Bee Eh team you would like to get news for.
    </audio>
    </prompt>

    <filled>
    <!-- ASSIGN THE CALLER'S TEAM NAME TO A DOCUMENT SCOPED VARIABLE -->
    <log expr="'***** USER INPUT FOR CHOOSETEAM = ' + lastresult$.interpretation.teamRSS"/>
    <assign name="document.teamRSS" expr="lastresult$.interpretation.teamRSS"/>
    </filled>
  </field>

  <field name="confirmTeam" type="boolean" modal="true">
  <prompt bargein="false">
    <audio src="youChose.wav">
      you chose the
    </audio>

    <audio expr="document.teamRSS + '.wav'">
      <value expr="document.teamRSS"/>
    </audio>

    <audio src="isCorrect.wav">
    is this correct?
    </audio>
  </prompt>

  <filled>
    <log expr="'***** USER INPUT FOR CONFIRMTEAM = ' + lastresult$.interpretation.confirmTeam"/>
    <if cond="confirmTeam == true">
    <!-- IF THE INPUT WAS RECOGNIZED CORRECTLY, FETCH THE TEAM RSS FEED -->
      <goto next="#NBAHeadlines"/>
      <else/>
      <!-- IF THE INPUT WAS NOT RECOGNIZED CORRECTLY, START THE DIALOG OVER -->
      <clear namelist="chooseTeam confirmTeam"/>
      <goto nextitem="chooseTeam"/>
    </if>
  </filled>
  </field>
</form>

<form id="NBAHeadlines">
  <block name="fetchData">
    <!-- WE USE THE DOCUMENT SCOPED VARIABLE TO CHOOSE WHICH RSS FEED WE WILL USE FROM NBA.com  -->
  <data name="fetchRSS" srcexpr="'http://www.nba.com/'+ document.teamRSS +'/rss.xml'"/>
  </block>
</form>
</vxml> 


To recap what we did above:
We created our VoiceXML document and declared three distinct <forms> for our three distinct steps in the application. We added voice recognition dialogs to allow the caller to choose and confirm the NBA team that they want news on, and we prepared a handful of document-scoped variables for holding the data we will eventually fetch from the RSS feed. One of these document-scoped variables will be used to determine which RSS feed that we fetch our data from in the <data srcexpr> element, (document.teamRSS). In our next step, we will flesh out our logic a bit more and explore how we use the <data> tag, add some ECMAScript to grab our third-party RSS information, and format it so that it's elegantly outputted to our caller.



Step 2: Fetching the RSS feed and formatting the text

Our next step is to add a grammar that allows the caller to choose a team. Since we want to stay as close as we can to a 'write-once, deploy anywhere' app, we will use the SRGS format and define a grammar that allows the caller to choose a team in any of the following formats:

"City, Team"
"City"
"Team"

As we don't foresee any teams being added to the NBA on a daily basis, we can create a simple, static file that covers all entries. We allow the aforementioned utterance flexibility by making use of the 'repeat' attribute of the <item> element.


        <item>
          <item repeat="0-1"> team city </item>
          <item repeat="0-1"> team name </item>
            <tag> teamRSS ='team name' </tag>  
</item>


SRGS grammars should be pretty familiar to everyone at this point, so we won't cover every aspect of the grammar creation. For anyone who is new to VoiceXML, if details are too sketchy, check our documentation of SRGS grammars to get up to speed.

Now that we have the basic structure clearly defined, we can now start adding details. The first order of business is to take a peek at our RSS feed for a given team. As Voxeo is based in Orlando, it seems appropriate to use the Tragic, er, I mean the Magic as our test case:

http://www.nba.com/magic/rss.xml

A few words about the <data> element: This multipurpose addition to the VoiceXML 2.1 specification can be used both to send information from a VoiceXML dialog to an external file and to grab external data from an XML formatted file and bring it into the VoiceXML context. In past tutorials, we have illustrated the usage of this element sending to a file, but in this tutorial, we will show how we grab data from a file, without using any server-side logic at all. In this case, all we need are some basic skills with JavaScript and an understanding of the Document Object Model (DOM, for short). This tutorial really shows off how powerful this element is and opens the door to former functionality that was impossible to implement without the usage of server-side logic. In short, this is possibly the most powerful addition to the VoiceXML 2.1 specification.

Boiling this data structure down to it's essentials, we end up with something like this:


<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">
<channel>
    <title>NBA.com: Magic News</title>
    <link>http://www.nba.com/</link>
    <description></description>
    <language>en-us</language>
    <copyright>Copyright (c) 2006 NBA Media Ventures....</copyright>
    <managingEditor>webmaster@nba.com</managingEditor>
  <image>
      <title>NBA.com</title>
      <url>http://stats.surfaid.ihost.com.....</url>
      <link>http://www.nba.com/</link>
      <width>94</width>
      <height>22</height>
  <item>
      <title><![CDATA[ Article Title 1 ]]></title>
      <link>Link to Article 1 on NBA.com</link>
      <description><![CDATA[Article Number 1 Body...we can assume that the Magic lost a game]]></description>
  </item>
 
  <item>
      <title><![CDATA[ Article Title 2 ]]></title>
      <link>Link to Article 2 on NBA.com</link>
      <description><![CDATA[Article Number 2 Body...we can assume that the Magic lost another game]]></description>
  </item>
<!--  ETC.... -->
</channel>
</rss>


As we are only concerned with outputting the article descriptions, the highlighted nodes above are all we need to be concerned wih capturing; everything else we can cheerfully ignore when we design our JavaScript function. However, note that we have some <description> nodes at the beginning of the document that we will want to skip over, so we will be taking this into account when we write up that function:


    <title>NBA.com: Magic News</title>
    ...
    <description></description>
    ...
      <title>NBA.com</title>
      ...


As hinted at previously, our JavaScript file is going to serve several purposes:


Using these constraints, we will construct an external .js file to hold our function. As an in-depth explanation of JavaScript is outside the scope of this tutorial, we  will not cover each and every line of the code in this case, but we will add some inline notes. For those wishing to gain a better understanding of JavaScript, it is suggested that you refer to a third-party tutorial.


// function #1: count the nodes

      // this counts how many headlines are available for a team
function getCount(d)  {
var x=d.getElementsByTagName("item");
              // invoked using "getCount(dataName)"

return x.length;
        // returns an integer holding the number of nodes, this gives us an "article count"
}


// function #2: grab the text data from the nodes and append them to an array

        function assignArray(d, n, r) {

          var j=(d.getElementsByTagName(r).length + 2);
          // remember that we need to "skip" over the first nodes named "description"
          var NBAarray;

            NBAarray = new Array();
            // declare an empty array
            for(var i = 0; i < j; i++) {
     
          // since its possible that a title or description entry from the RSS feed
          // will be empty, or have 'bad' characters, we need to do some error handling


            try {
  NBAarray[i] = d.getElementsByTagName(n).item(i).firstChild.data;
          // loop through the node values and append each entry to our comma-delimited array
            }
          // if the node value is undefined...
            catch(e)
            {
          //..then assign its value as an empty string to prevent the app from crashing
            NBAarray[i] = '';
            }
      }
          return NBAarray;
        // return the array holding all values from the node in question
}



Next thing on the boards is to include our script at the document scope and invoke our functions within the VoiceXML:



<?xml version="1.0" encoding="UTF-8"?>
<vxml version="2.1" xmlns:voxeo="http://community.voxeo.com/xmlns/vxml">

<!-- DECLARE SCRIPT THAT PARSES THROUGH THE XML NEWSFEED DATA -->
<script src="parseRSSData.js" fetchtimeout="15s"/>

  <var name="teamRSS"/>
  <var name="dataRSS"/>
  <var name="descriptionArrayRSS"/>
  <var name="fetchRSS"/>
  <var name="resultCount"/>


<form id="Intro">
  <block>
    <prompt bargein="true">
    <audio src="welcome.wav">
      Welcome to the real time Basketball news, where you can get up to date
      information on the recent happenings for your favorite En Bee Eh team.
    </audio>
    </prompt>
    <goto next="#Team"/>
  </block>
</form>

<form id="Team">
  <field name="chooseTeam" modal="true">
    <grammar src="TeamGram.xml" type="application/srgs+xml"/>
    <prompt baregin="true">
    <audio src="chooseTeam.wav">
      To begin, please choose the name of the En Bee Eh team you would like to get news for.
    </audio>
    </prompt>
    <filled>
    <log expr="'***** USER INPUT FOR CHOOSETEAM = ' + lastresult$.interpretation.teamRSS"/>
    <assign name="document.teamRSS" expr="lastresult$.interpretation.teamRSS"/>
    </filled>
  </field>

  <field name="confirmTeam" type="boolean" modal="true">
  <prompt bargein="false">
    <audio src="youChose.wav">
      you chose the
    </audio>

    <audio expr="document.teamRSS + '.wav'">
      <value expr="document.teamRSS"/>
    </audio>

    <audio src="isCorrect.wav">
    is this correct?
    </audio>
  </prompt>

  <filled>
    <log expr="'***** USER INPUT FOR CONFIRMTEAM = ' + lastresult$.interpretation.confirmTeam"/>
    <if cond="confirmTeam == true">
      <goto next="#NBAHeadlines"/>
      <else/>
      <clear namelist="chooseTeam confirmTeam"/>
      <goto nextitem="chooseTeam"/>
    </if>
  </filled>
  </field>
</form>

<form id="NBAHeadlines">
  <block name="fetchData">

  <data name="fetchRSS" srcexpr="'http://www.nba.com/'+ document.teamRSS +'/rss.xml'"/>
  <!--  ASSIGN OUR DATA FETCH TO A VOICEXML VARIABLE -->
  <assign name="document.dataRSS" expr="fetchRSS.documentElement"/> 
  <!--  INVOKE OUR RESULT COUNT FUNCTION, AND IMPORT THE VALUE TO A VOICEXML VARIABLE -->
  <assign name="document.resultCount" expr="getCount(fetchRSS)"/>
  <!--  INVOKE OUR ASSIGNARRAY FUNCTION, AND IMPORT THE ARRAY TO A VOICEXML VARIABLE -->
  <assign name="document.descriptionArrayRSS" expr="assignArray(fetchRSS, 'description', 'item')"/>
  <!--  CLIP OFF THE FIRST ARRAY VALUE, (TO IGNORE THE FIRST INSTANCE OF 'DESCRIPTION' -->
  <assign name="document.descriptionArrayRSS" expr="document.descriptionArrayRSS.slice(1, -1);"/>

    <prompt>
    <audio src="weHave.wav">
      We have
    </audio>

    <audio expr="document.resultCount + '.wav'">
      <value expr="document.resultCount"/> 
    </audio>
   
    <audio src="artAvail.wav">
      articles available for the
    </audio>   

    <audio expr="document.teamRSS + '.wav'">
      <value expr="document.teamRSS"/>
      <break size="small"/>
    </audio>
    </prompt>
  </block>
</form>
</vxml> 



While our code is growing exponentially in length, we really didn't add all that much; most of our additions are for playback of TTS or wav files. The 'guts' of what we added are the aforementioned JS file creation, the reference to it within the VoiceXML file, (via the <script> element), and the invocation of the function calls (the <var> assignations just below the <data> element). Now, all that remains to is to output our array values to the caller and add a final bit of polish to our menu structure.


Step 3: Looping through our array of values


Now that all the really hard work is done, we can take a look at that other addition to the 2.1 specification, the illustrious <foreach> element. This element is probably familiar to our advanced developers out there; a "foreach" loop structure is used in a number of client-side and server-side scripts. The inclusion of this element in the 2.1 specification allows developers an easy way of looping through a comma-delimited list of array values and outputting them sequentially via TTS, <log> statements, and the like. To further illustrate, let's look at some dummy data in a true array format:


<!-- "myArray" -->
[value 1, value 2, value 3, value 4]


Using the <foreach> element, we can then output these values one by one to our callers and add a pause between each "loop" of the values:


    <foreach item="arrayItem" array="myArray">
    <prompt>
      <value expr="arrayItem"/>
      <break size="medium"/>
    </prompt>     
    </foreach>


This would be rendered to the caller via TTS as:

"value 1, (pause), value 2, (pause), value 3, (pause), value 4, (pause)"

Using this as a simplified example, we now get an idea as to how this applies to our array of NBA news articles. So, let's take a look at how we can apply this new-found knowledge:


<?xml version="1.0" encoding="UTF-8"?>
<vxml version="2.1" xmlns:voxeo="http://community.voxeo.com/xmlns/vxml">

<script src="parseRSSData.js" fetctimeout="15s"/>

  <var name="teamRSS"/>
  <var name="dataRSS"/>
  <var name="descriptionArrayRSS"/>
  <var name="fetchRSS"/>
  <var name="resultCount"/>

  <!-- DECLARE OUR DOCUMENT SCOPED LINK GRAMMAR -->
  <link next="#Team">
  <grammar root="main">
    <rule id="main" scope="public">
    <one-of>
    <item> start over </item>
    <item> main menu </item>
    <item> main </item> 
    </one-of>
    </rule>
  </grammar>
  </link>



<form id="Intro">
  <block>
    <prompt bargein="true">
    <audio src="welcome.wav">
      Welcome to the real time Basketball news, where you can get up to date
      information on the recent happenings for your favorite En Bee Eh team.
    </audio>
    </prompt>
    <goto next="#Team"/>
  </block>
</form>

<form id="Team">
  <field name="chooseTeam" modal="true">

    <grammar src="TeamGram.xml" type="application/srgs+xml"/>

    <prompt baregin="true">
    <audio src="chooseTeam.wav">
      To begin, please choose the name of the En Bee Eh team you would like to get news for.
    </audio>
    </prompt>

    <filled>
    <log expr="'***** USER INPUT FOR CHOOSETEAM = ' + lastresult$.interpretation.teamRSS"/>
    <assign name="document.teamRSS" expr="lastresult$.interpretation.teamRSS"/>
    </filled>
  </field>

  <field name="confirmTeam" type="boolean" modal="true">
  <prompt bargein="false">
    <audio src="youChose.wav">
      you chose the
    </audio>

    <audio expr="document.teamRSS + '.wav'">
      <value expr="document.teamRSS"/>
    </audio>

    <audio src="isCorrect.wav">
    is this correct?
    </audio>
  </prompt>

  <filled>
    <log expr="'***** USER INPUT FOR CONFIRMTEAM = ' + lastresult$.interpretation.confirmTeam"/>
    <if cond="confirmTeam == true">
      <goto next="#NBAHeadlines"/>
      <else/>
      <clear namelist="chooseTeam confirmTeam"/>
      <goto nextitem="chooseTeam"/>
    </if>
  </filled>
  </field>
</form>



<form id="NBAHeadlines">
  <block name="fetchData">
  <data name="fetchRSS" srcexpr="'http://www.nba.com/'+ document.teamRSS +'/rss.xml'"/>

  <assign name="document.dataRSS" expr="fetchRSS.documentElement"/> 
  <assign name="document.resultCount" expr="getCount(fetchRSS)"/>
  <assign name="document.descriptionArrayRSS" expr="assignArray(fetchRSS, 'description', 'item')"/>
  <assign name="document.descriptionArrayRSS" expr="document.descriptionArrayRSS.slice(1, -1);"/>

    <prompt>
    <audio src="weHave.wav">
      We have
    </audio>

    <audio expr="document.resultCount + '.wav'">
      <value expr="document.resultCount"/> 
    </audio>
   
    <audio src="artAvail.wav">
      articles available for the
    </audio>   

    <audio expr="document.teamRSS + '.wav'">
      <value expr="document.teamRSS"/>
      <break size="small"/>
    </audio>

    <!-- ADD PROMPT THAT INSTRUCTS CALLER ABOUT THE GLOBAL COMMAND -->
    <audio src="content.wav">
      all content courtesy of En Bee Eh dot com.
      To choose another team, you can say main menu at any time.
    </audio>
    <break size="medium"/>

    </prompt>

    <!-- SPECIFY OUR DOCUMENT SCOPED VAR AS THE ARRAY VALUE -->
    <foreach item="article" array="document.descriptionArrayRSS">
    <prompt>
      <!-- THE 'ARTICLE' ATTRIBUTE HOLDS THE ARRAY VALUE -->
      <!-- CORRESPONDING TO THE CURRENT ITERATION OF THE LOOP -->

      <value expr="article"/>
      <break size="medium"/>
    </prompt>     
    </foreach>

    <prompt bargein="true">
      <audio src="noMore.wav">
      There are no more news items available for the
      </audio>

    <audio expr="document.teamRSS + '.wav'">
      <value expr="document.teamRSS"/>
      <break size="small"/>
    </audio>

    <audio src="moreInfo.wav">
      For more information on your favorite teams, please visit  h t t p w w w dot En Bee Eh dot com
      <break size="small"/>
    </audio>

    <audio src="returnToMain.wav">
      We will now return you to the main menu, where you can get up to date news items for another En Bee Eh team.
      Or, you may hang up, if you are finished.
    </audio>
    </prompt>

  <!-- NAVIGATE TO DUMMY FIELD -->
    <goto nextitem="Dummy"/>

  </block>


  <field name="Dummy" type="boolean">
  <!-- MAKE THE FIELD TIMEOUT -->
  <property name="timeout" value="0.5s"/>
  <!-- CATCH THE TIMEOUT, AND RETURN TO THE START -->
  <noinput>
    <goto next="NBA_newsreader.xml"/>
  </noinput>
  <filled/>
  </field>

</form>
</vxml> 



We snuck in another addition in this step -- that being some functionality to the navigation options available to our caller. What happens if the caller is 20 articles deep into Orlando Magic news and he or she wants to choose another team; do we make them hang up, and dial in again? Heck, no. We added a <link> grammar at the document scope that will allow our callers to say "main menu," (or any of the alternate utterances listed), in order to allow the caller to return to the "choose team" dialog. Within this element, we simply <goto> the <form> where the "choose team" dialog is, and the caller can get news on their second-favorite team.
It's important to note that <form id="NBAHeadlines"> does not have an active voice reco <field>, so the recognizer won't be listening for input....for all practical purposes, it is deaf and dumb to our commands of "main menu." However, we can get around this entirely with a "dummy" <field> that is specifically designed to be skipped over, (hence, the "timeout" <property> setting, and the logic within the <nomatch> handler. Pretty slick, eh?


Step 4: Dial In and Test Out Your Code

All that remains now is to test your code. If you are using the Voxeo hosted platform, simply use the File Manager to upload your code onto our free hosting environment, or you can host it on your own servers. If you are using a local instance of the Prophecy platform, then you can place the files in your 'www' subdirectory and change your call routing to point to this file.

If you have any difficulties with your hand-typed code from this tutorial, you can always grab the zip file below to use as a comparison.

Download the Code!

  Source code

What we learned:



  ANNOTATIONS: EXISTING POSTS
carmenati
6/1/2006 1:24 AM (EDT)
is the rss's url valid?
isn't it www.nba.com/rss/nba_rss.xml instead of www.nba.com/rss.xml?!
MattHenry
6/1/2006 5:23 PM (EDT)

Hi there,

I don't see where this URL is referenced in our tutorials. Note that within the application itself, we are defining a variable that accessing the correct team name for the RSS feed to be fetched, ie:

<data name="fetchRSS" srcexpr="'http://www.nba.com/'+ document.teamRSS +'/rss.xml'"/>


..which could then equate to:

http://www.nba.com/magic/rss.xml
http://www.nba.com/jazz/rss.xml
(etc.)

Hope that this helps to clarify!

~Matthew Henry




carmenati
6/2/2006 2:15 AM (EDT)
hi matt,
now it's clear...
tks

login
  tutorial XML Grammars  |  TOC  |  tutorial Using the Mark Element  

© 2008 Voxeo Corporation  |  Voxeo IVR  |  VoiceXML & CCXML IVR Developer Site