Using the data element
Last Updated: 04-22-2009

Typical data-driven Web applications use server-side frameworks and a database API to access data and merge it with user-interface code for presentation to the user. Examples include Java Server Pages (JSP) and JDBC, Microsoft Active Server Pages (ASP) and ActiveX Data Objects (ADO), and Apache mod_perl and DBI.

While this methodology can also be applied to building VoiceXML-based voice applications, Tellme provides an alternative, a data element that allows you to fetch and manipulate XML data from your VoiceXML code using the standard Document Object Model (DOM). This document shows you how to use the data element so that you can build powerful voice applications using VoiceXML, JavaScript, and a small amount of server-side code.

1. Advantages of the data element

Using the data element provides several advantages over dynamically generating your VoiceXML documents on the server.

1.1. Caching

By using static VoiceXML documents, the VoiceXML interpreter can safely and easily cache those documents. Most Web applications built using server-side scripts that merge data and user-interface code do so upon every request - even if the data hasn't changed. This underutilizes one of the browser's key features - caching. Alternatively, if the document remains static and only the data changes, the browser can cache the former until modified and request the latter, saving significant network bandwidth.

1.2. Conserve network bandwidth

By reducing the amount of content passed between client and server, you save network bandwidth. In the world of computer networking, less is more. That is, the fewer network requests you make, and the less data you need transferred, the better the network will perform for everyone. One of the problems with generating all of the content on the server is that you need to send it all across the wire for execution on the client. Lots of static content, those bits that aren't stored in the database, are transferred repeatedly.

1.3. Conserve server resources

By fielding simple requests for data and leaving data manipulation to the client, you reduce the load on your application server. When you use your application server to retrieve your data and merge it with your user interface, you're doing most of the work on the server, impeding its ability to service other requests.

1.4. Modularize and reuse data access code

By separating data access from user interface, your application code will be cleaner. In addition, multiple applications can reuse the data access component through a simple URL-based API.

1.5. Ease error handling

When generating VoiceXML using a server-side script, latency issues or failures involving the backend database, or errors in the generated VoiceXML can result in a failure to deliver both the data and the VoiceXML content to the VoiceXML interpreter. Using the data element under these circumstances, because it doesn't require a transition to another VoiceXML dialog, only delivery of the data will fail, and you can handle these errors more easily and robustly. See Handling errors for details on error handling when using the data element.

2. How it works

The data element fetches an XML document by making an HTTP request for the URL associated with its src attribute. If the URL is successfully retrieved, the interpreter parses the XML document, and exposes it to your VoiceXML code in Document Object Model (DOM) format. The DOM is a set of objects representing the elements and attributes of an XML document. Using JavaScript, you make calls into the object model to traverse the document and access the data associated with those elements and attributes.

3. Formatting the data

If you're building a data-driven voice application, your data might originate from a traditional database management system (DBMS) such as Oracle or from a data feed licensed from a third party such as TIBCO. To access this data using the data element, you need to retrieve the data you want from the DBMS or feed and convert it from its native format into XML.

For example, consider a university registration application. To narrow down the variety of classes available at the institution, the application allows the user to hear the classes within a particular subject area. The application lists the subjects and allows the user to make a selection. The application uses the data element to make an HTTP request to a CGI that selects the list of subjects from a database. The XML returned by the CGI might look like the following:

<?xml version="1.0"?>
<?access-control allow="*"?>
<subjects>
   <subject id="s1">
      <name>Overview</name>
   </subject>
   <subject id="s2">
      <name>Design</name>
   </subject>
   <subject id="s3">
      <name>Audio</name>
   </subject>
   <subject id="s4">
      <name>Development</name>
   </subject>
   <subject id="s5">
      <name>Tuning</name>
   </subject>
</subjects>

As another example, consider a simple stock quote application. The application prompts the user for a company name. The application's grammar maps a company name to its corresponding ticker symbol. Given the ticker symbol, the application uses the data element to make an HTTP request for the current stock quote information associated with that ticker symbol. The URL specified in that HTTP request references a CGI that accesses the stock quote data feed for the specified symbol, converts the data to XML, and sends it back to the application. The XML returned by the CGI might look like the following:

<?xml version="1.0"?>
<?access-control allow="*"?>
<stock id="SUNW">
   <name>Sun Microsystems</name>
   <last>14.24</last>
   <change>0.43</change>
   <yearlow>7.52</yearlow>
   <yearhigh>48.12</yearhigh>
   <daylow>13.62</daylow>
   <dayhigh>14.47</dayhigh>
   <volume>75.15</volume>
   <peratio>172.60</peratio>
</stock>

In the previous examples, observe the XML declaration that occurs at the beginning of each XML document. According to section 2.8, Prolog and Document Type Declaration, of the XML specification, "an XML document SHOULD begin with an XML declaration specifying the version of XML being used." While the XML declaration is optional, if you include it, it must appear at the beginning of the document. No whitespace may precede it or the interpreter throws an error.badfetch.

For information about the "access-control" processing instruction (PI) contained in each of these documents, please refer to Securing your data.

4. Requesting the data

To request your data, set the src or srcexpr attribute of the data element to the URL of the CGI that serves the XML data. The former allows you to specify a hard-coded URL. The latter allows you to specify a JavaScript expression that evaluates to a URL.

The following data element references a CGI script that lists the subjects at a fictitious university.

<data name="domSubjects" src="http://data.acmeu.edu/getsubjects.cgi" />

The following data element references a CGI script that returns quote information for a stock. The srcexpr attribute is used to allow the ticker symbol to be specified dynamically at runtime. The native JavaScript function encodeURIComponent() ensures that the ticker value is encoded properly for transfer across the Web.

<data name="domQuote" 
   srcexpr="'http://www.acmequotes.net/getquote.cgi?details=1&amp;ticker=' + 
      encodeURIComponent(symbol)" />

If your variable names match your CGI parameters, you can use the namelist attribute instead. In this case, there's no need to manually encode the values; the VoiceXML interpreter encodes the variables specified via the namelist automatically.

<data name="domQuote" src="http://www.acmequotes.net/getquote.cgi" namelist="symbol" />

If your variables don't match the names of your CGI parameters, you can use a temporary variable that matches the name of the CGI parameter.

<var name="ticker" expr="symbol"/>
<data name="domQuote" src="http://www.acmequotes.net/getquote.cgi" namelist="ticker" />

Typically, the implementation details of the CGI are unimportant. VoiceXML developers need only be familiar with the parameters that the CGI accepts and the schema of the XML document that the CGI returns. If, however, your voice application needs to submit large quantities of binary data or text containing non-ASCII characters to an HTTP server, consider setting the method attribute to "POST" and setting the enctype attribute to "multipart/form-data". In that case, the CGI to which you are submitting the request needs to be specifically designed to handle that encoding. For more details on the "multipart/form-data" encoding, consult RFC 2388.

Note. When submitting a variable that references recorded audio the VoiceXML interpreter automatically use the HTTP "POST" method and submits the data using the "multipart/form-data" encoding.

5. Manipulating the data

Once the VoiceXML interpreter retrieves the data referenced by the data element and parses it into a DOM, your application code can access that data programmatically via the variable associated with the name attribute of the data element. That variable references the document object, the top-level object exposed by the DOM. By calling methods and accessing properties of the document object, you can access the elements and attributes of the XML document it represents.

For example, recall the data element that references an XML document describing the list of subjects shown in the section Formatting the data. The interpreter assigns a reference to the DOM document object to the variable domSubjects.

<data name="domSubjects" src="http://data.acmeu.edu/getsubjects.cgi" />

5.1. Obtaining the root document element

The root document element is the top-level element of an XML document. To obtain a reference to the root document element access the documentElement property of the document object.

<data name="domSubjects" src="..."/>
<var name="root" expr="domSubjects.documentElement"/>

5.2. Accessing child elements

To access the individual nodes contained by the root document element, iterate through the childNodes. The childNodes property retrieves a NodeList, a collection of Node objects. A NodeList is like an array. You determine the number of elements in a NodeList by checking its length property, but you iterate through the individual elements in the NodeList using the item method. Like arrays, the index into a NodeList is zero-based. Using the subjects schema described above, the following example iterates through the subject nodes contained by the root document element (subjects).

for (var iNode = 0; iNode < root.childNodes.length; iNode++)
{
  var oSubject = root.childNodes.item(iNode);
  // manipulate the subject node
}

The childNodes NodeList consists of a list of Node objects. Each node represents a subtree that may contain zero or more Node objects. Like the root document element, each Node object exposes a childNodes property that exposes a NodeList of zero or more Node objects. Given a subject node, drill into its childNodes NodeList to retrieve the name Node of the subject.

var oSubject = root.childNodes.item(0); // first subject
var oName = oSubject.childNodes.item(0); // name

You can verify that the variable oName references the subject's name node by checking its nodeName property.

if (oName.nodeName != "name") // error

5.3. Understanding node types

An XML document not only consists of elements and attributes but also text, comments, CDATA sections, processing instructions, entities, DOCTYPE declarations, and notations. Because the DOM represents these constructs as generic Node objects, each Node object exposes a nodeType property. The property returns an integer that identifies the type of node. The Node interface defines constants accessible via JavaScript for each of these node types. By using the constants, your code will be more readable.

The following table lists the different types of nodes exposed by the Tellme implementation of the DOM, the integer returned by the nodeType property, the corresponding constant, and a brief description of the construct. For a more detailed description of these constructs, see the XML specification.

Element 1 Node.ELEMENT_NODE The node represents an element.
Attribute 2 Node.ATTRIBUTE_NODE The node represents an attribute of an element.
Text 3 Node.TEXT_NODE The node represents the text content of a tag.
CDATA section 4 Node.CDATA_SECTION_NODE The node represents a CDATA section in the XML source. The node represents a CDATA section.
Processing instruction 7 Node.PROCESSING_INSTRUCTION_NODE The node represents a processing instruction.
Text 8 Node.COMMENT_NODE The node represents a comment.
Document 9 Node.DOCUMENT_NODE The node represents a document object.

The Tellme VoiceXML interpreter does not expose the following constructs via the DOM: entity references, entities , document type declarations, document fragments, and notations.

5.4. Retrieving text

The DOM represents the text contained between a start tag and an end tag as a special type of Node object, a text node. To get the value of a text node, access the data property.

var oText = oName.childNodes.item(0);
var subject;
if (oText.nodeType == Node.TEXT_NODE)
{
   subject = oText.data;
}

Using the childNodes NodeList to retrieve the text contained by a node containing a text node is cumbersome. If you're confident that the structure of the data returned by the CGI that serves your data is constant, you can use some shortcuts. The following example uses the firstChild property of the Node to retrieve its first and only child, a text node.

var subject = oName.firstChild.data;

You can be even more succinct by using the text property, a Tellme extension to the standard DOM. This property retrieves all the text contained by the Node, saving you the trouble of recursively walking the subtree to gather the text nodes yourself.

var subject = oName.text;

5.5. Retrieving attributes

A Node object exposes its attributes in three ways: via the attributes property and the getAttribute and getAttributeNS methods. Use getAttribute if you know the name of the attribute for which you want the value. Use getAttributeNS to retrieve an attribute with a specified local name and namespace URI. The attributes property returns an Attributes object, a collection of attribute objects. An attribute object exposes a name and a value property.

Given a subject node, you can obtain the value of its id attribute as follows:

var id = oSubject.getAttribute("id");

5.6. Accessing elements more quickly

If you have control over the XML document generated by your data server, you can achieve higher performance during DOM manipulation by leveraging a simple indexing facility provided by the Tellme VoiceXML intepreter.

To enable the indexing facility, you add id attributes to the elements in your XML document to which you desire quick access. The value of each id attribute must be unique within the document. If a value is used more than once, only the first node using that id will be indexed. The following example revises the stock quote schema described above to specify an id attribute for each child node of the root document element.

<?xml version="1.0"?>
<?access-control allow="*"?>
<stock id="SUNW">
   <qd id="name">Sun Microsystems</qd>
   <qd id="last">14.24</qd>
   <qd id="change">0.43</qd>
   <qd id="yearlow">7.52</qd>
   <qd id="yearhigh">48.12</qd>
   <qd id="daylow">13.62</qd>
   <qd id="dayhigh">14.47</qd>
   <qd id="volume">75.15</qd>
   <qd id="peratio">172.60</qd>
</stock>

To access a node by id, use the getElementById method. The following example retrieves the name and current value of the stock quote described above.

<vxml version="2.1">
<form id="form1">
<data name="oData" src="..."/>
<block>
  <audio><value expr="oData.getElementById('name').text"/>
  is trading at
  <value expr="oData.getElementById('last').text"/>.
  </audio>
</block>
</vxml>

6. Scope of the data element

The data element can be specified at application, document, dialog, and anonymous scope. data element execution is determined by the form interpretation algorithm (FIA) and is treated like a var or script element. For example, when the data element is declared as a child of the form element (i.e. at dialog scope), it is executed before the first form item (e.g. block or field) within the dialog.

In the following example, the data element at document scope (data1) is executed after the var element that declares the variable src1 but before the script element that declares the variable root. Following execution of the var, data, and script elements at document scope, the form1 dialog is processed. The data element at dialog scope (data2) is executed after the script element that declares the variable src2 but before the var element that declares the variable named top. The data element declared at dialog scope is also executed prior to form interpretation; hence, the block element (block1) is executed after the data element.

<vxml version="2.1">
  <var name="src1" expr="'http://data.acme.net/getdata.cgi'"/>
  <data name="data1" srcexpr="src1"/>
  <script>
   var root = "data1.documentElement";
  </script>

  <form id="form1">
    <script>
     var src2 = "http://data.acme.net/getdata.cgi";
   </script>
    <data name="data2" srcexpr="src2"/>
    <var name="top" expr="data2.documentElement"/>

    <block name="block1">
      <value expr="top.firstChild.data"/>
    </block>
  </form>
</vxml>

Note. Even if the data, var, or script element followed the block, in form1 in document source order, each would be initialized before the block was executed.

When the data element is executed, the DOM corresponding to the data that it references exists as long as the scope in which it is contained exists. A DOM will continue to exist as long as there are outstanding references to it. Thus, if a reference to the DOM is assigned to another variable at a higher scope, the DOM will continue to exist even after the scope in which it was created is destroyed.

In the following example, the data element is executed in the anonymous scope of the block element. The DOM is then assigned to a variable created at document scope. The DOM is later accessed in form2 and can continue to be accessed while the document is in scope.

<vxml version="2.1">
<var name="data1"/>

<form id="form1">
<block>
   <data name="data1" src="..."/>
   <assign name="document.data1" expr="data1"/>
   <goto next="#form2"/>
</block>
</form>

<form id="form2">
<block>
   <!-- manipulate the DOM -->
   <audio><value expr="data1.documentElement.firstChild.data"/></audio>
</block>
</form>

</vxml>

7. Handling errors

If the interpreter is unable to fetch the XML document referenced by the data element, or it retrieves the document but the document is not valid XML, the interpreter throws an error.badfetch event. Use the catch element to handle this error.

<form id="fetch">
  <catch event="error.badfetch">
     <audio>I'm sorry. We're experiencing technical difficulties.</audio>
     <disconnect/>
  </catch>

  <data src="..." name="d1" />
  <block>
     <goto next="#manipulate" />
  </block>
</form>

In addition, the interpreter will throw an exception if you attempt to access non-existent elements. Consider the following XML document representing an incomplete stock quote:

<stock id="KKD">
   <name>Krispy Kreme Doughnuts</name>
</stock>

The following example code retrieves the data and attempts to retrieve a non-existent node containing the current value:

<vxml version="2.1">
  <data name="oData" src="http://www.acmequotes.net/getquote.cgi" namelist="ticker"/>
  <form>
    <block>
     <var name="coname" 
     expr="oData.documentElement.childNodes.item(0).firstChild.data"/>
     <var name="last" expr="oData.documentElement.childNodes.item(1).firstChild.data"/>
     <audio><value expr="coname"/> is trading at $<value expr="last"/></audio>
    </block>
  </form>
</vxml>

You can protect your VoiceXML application from these exceptions by using JavaScript exception handling. The following example demonstrates the use of exception handling.

<vxml version="2.1">
<form>
<data name="oData" src="http://www.acmequotes.net/getquote.cgi" namelist="ticker"/>
<block>
<script>
   var root = oData.documentElement;
   var coname, last, hadError = false;
   try {
      coname = root.childNodes.item(0).firstChild.data;
      last = root.childNodes.item(1).firstChild.data;
   }
   catch(e)
   {
     hadError = true;
   }
</script>
   <if cond="hadError">
      <audio>I'm sorry, but we were unable to retrieve your quote.</audio>      
   <else/>
      <audio><value expr="coname"/> is trading at $<value expr="last"/></audio>
   </if>
</block>
</form>
</vxml>

8. Encapsulating access to data

While exception handling can help you avoid unexpected errors, your code will be cleaner and more maintainable if you encapsulate access to data using JavaScript functions or objects. Given the stockquote example, you can define a JavaScript class that provides access to the quote data via individual methods. If the company providing the stockquote data feed advertises a new XML format available by referencing a new URL, you need only modify your data element and the user-defined class that manipulates that data.

The following example utilizes a JavaScript class that encapsulates access to a stockquote. The code calls methods on the quote object to retrieve the name of the stock (GetName()) and the current value per share (GetLast()).

<?xml version="1.0"?>
<vxml version="2.1">

<script src="quote.js"/>
<script>
  // JS object encapsulating quote data
  var oQuote = new CQuote();
</script>

<!-- fetch the quote -->
<form id="fetchquote">
<data src="sunw.exml" name="oData"/>
<block>
<script>oQuote.Init(oData)</script>
<goto next="#readquote"/>
</block>
</form>

<!-- read back the quote -->
<form id="readquote">
<block>
  <audio>
    <value expr="oQuote.GetName()"/> is trading at 
    $<value expr="oQuote.GetLast()"/>
  </audio>
</block>
</form>

</vxml>


The full definition of a stockquote class based on the schema defined in the section entitled Formatting the data follows. To use this class, use the script element to source in a document containing the class definition. In your VoiceXML code, create an instance of the class and call the Init method passing a data element variable that references a stockquote conforming to the previously defined schema. The Init method walks the DOM exposed by the data element and creates properties corresponding to each child node of the root document element. Other methods of the user-defined quote object access these properties to return quote data.

// Encapsulate a stock quote

// static method to create a new quote object
CQuote.Create = function(oData)
{
   var oQuote = new CQuote();
   return (oQuote.Init(oData) ? oQuote : null);
}

// c-tor
function CQuote() {}

// Initialize the quote object with an object encapsulating a DOM
CQuote.prototype.Init = function(oData)
{   
   this._data = {}; // initialize data stash

   if (!oData || 'object' != typeof(oData))
   {
      return false;
   }

   var oRoot = oData.documentElement;

   this._data.symbol = oRoot.getAttribute("id");

   for (var i = 0; i < oRoot.childNodes.length; i++)
   {
      var oChild = oRoot.childNodes.item(i);
      var sName = oChild.nodeName;
      try {
         this._data[sName] = oChild.firstChild.data;
      }
      catch(e)
      {
         CQuote.Log("Unable to read data for '" + sName + "'");
      }
   }

   return true;
}

// has the value of the equity changed today?
CQuote.prototype.HasChanged = function()
{
	return (0 == this.GetChange() ? false : true);
}

// Return the equity's symbol
CQuote.prototype.GetSymbol = function()
{
	return this._data.symbol;
}

// Return the name of the equity
CQuote.prototype.GetName = function()
{
   return (this._data.name || "");
}

// Return the latest price
CQuote.prototype.GetLast = function()
{
   return (this._data.last || 0);
}

// Return the change in price from the open
CQuote.prototype.GetChange = function()
{
   return (this._data.change || 0);
}

// Return the year high
CQuote.prototype.GetYearHigh = function()
{
  return (this._data.yearhigh || 0);
}

// Return the year low
CQuote.prototype.GetYearLow = function()
{
  return (this._data.yearlow || 0);
}

// Return the day high
CQuote.prototype.GetDayHigh = function()
{
   return (this._data.dayhigh || 0);
}

// Return the day low
CQuote.prototype.GetDayLow = function()
{
   return (this._data.daylow || 0);
}

// Return day's volume
CQuote.prototype.GetVolume = function()
{
   return (this._data.volume || 0);
}

// Return price to earnings ratio
CQuote.prototype.GetPERatio = function()
{
   return (this._data.peratio || 0);
}

CQuote.Log = function(s)
{
  vxmllog(s);
}

9. Securing your data

Data security is vital to businesses, and it is equally important to customers. To ensure the privacy of your data as it is transmitted between your servers and Tellme's VoiceXML interpreter, Tellme recommends that you use the Secure Sockets Layer (SSL). Tellme attempts to communicate with an HTTP server using SSL 3.0 when you reference a URL using the HTTPS protocol.

In addition, because the Tellme VoiceXML interpreter executes content retrieved from other servers outside the Tellme Network, Tellme enforces security policies embedded in your data via the required access-control processing instruction (PI). By providing a list of the hosts and domains to which you wish to provide access to your data in the data itself, the VoiceXML interpreter allows you to share that data with applications running on authorized hosts and to deny access to those that are not.

To make data available to the Tellme VoiceXML interpreter via the data element, you must include a single processing instruction (PI) named access-control that specifies the access policy for that content. The allow attribute of the PI identifies the hosts or domains allowed to manipulate that content via the Tellme VoiceXML interpreter. When the content is retrieved, the URL of the requesting page will be matched against the allowed hosts or domains. If there is no access-control PI or the host portion of the URL does not match a host or domain listed in the allow attribute of the PI, the interpreter denies the request and throws the event error.noauthorization.

In the following example, the hosts named voice.roadrunner.edu and voice.acme.edu are allowed access to the data. A data request from voice.coyote.net to acme.coyote.net will fail because the PI does not specify a host on coyote.net.

<?access-control allow="voice.roadrunner.edu voice.acme.edu"?>

Numerous hosts within a domain may require data access, and listing them all is impractical. For this reason, the VoiceXML interpreter supports wildcard matching through the use of an asterisk (*) at the beginning of a domain name. In the following example, all hosts within the roadrunner.edu and acme.net domains are allowed access to the data in which the PI is contained:

<?access-control allow="*.roadrunner.edu *.acme.edu"?>

To allow any host in any domain to access the data, set the value of the allow attribute of the access-control PI to a single asterisk (*) as demonstrated in the following example:

<?access-control allow="*"?>

More information about the handling of access-control processing instructions both prior to revision 3 and in revision 3 and later is provided here.

See Also
An XML Primer, Tellme Document Object Model Reference
[24]7 Inc.| Terms of Service| Privacy Policy| General Disclaimers