Volume 4, Issue 3 - September / Octoberl 2004
 
   
 

In this monthly column, an industry expert will answer common questions about VoiceXML and related technologies. Readers are encouraged to submit questions about VoiceXML, including development, voice-user interface design, and speech technology in general, or how VoiceXML is being used commercially in the marketplace. If you have a question about VoiceXML, e-mail it to and be sure to read future issues of VoiceXML Review for the answer.

By Matt Oshry

Q: Given an ECMAScript object, how do I submit that object to a document server for further processing?

A: Section 5.3.8 of the VoiceXML 2.0 specification (http://www.w3.org/TR/2004/REC-voicexml20-20040316/#dml5.3.8) states the following:

"If the variable is an ECMAScript Object the mechanism by which it is submitted is not currently defined. The mechanism of ECMAScript Object submission is reserved for future definition."

So, it's entirely up to the implementation platform as to how it serializes objects that you submit directly, but it also provides a hint as to how you might submit the individual properties yourself:

"Instead of submitting ECMAScript Objects directly, the application developer may explicitly submit
properties of Object as in 'date.month date.year'."

This works fine if you know the set of the properties ahead of time. For example, consider a subdialog that retrieves a credit card number and expiration date (month and year) from the user and returns these values using the property names "ccnum", "expmonth", and "expyear":



<vxml version="2.0"
  xmlns="http://www.w3.org/2001/vxml">
  <form id="get_cc_info">
    <field name="ccnum"/>
    <field name="expmonth"/>
    <field name="expyear"/>
    <block>
      <return namelist="ccnum expmonth expyear"/>
    </block>
  </form>
</vxml>

The calling <subdialog>, cognizant of the properties returned by the subdialog, can submit them directly:

<vxml version="2.0"
  xmlns="http://www.w3.org/2001/vxml">
  <form>
    <subdialog name="ccinfo" src="cc.vxml">
      <filled>
        <submit next="cc.cgi"
          namelist="ccinfo.ccnum ccinfo.expmonth ccinfo.expyear"/>
      </filled>
    </subdialog>
  </form>
</vxml>
 

A CGI on the server can easily access these values. For example, in Perl:


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

use strict;
use CGI qw(param);

my $ccnum = param("ccinfo.ccnum");
my $month = param("ccinfo.expmonth");
my $year = param("ccinfo.expyear");

# ... more code to write the info to a DBMS
# and generate a VoiceXML response page

 

Q. What if I don't know the set of properties exposed by the object a priori?
For example, what if I call a subdialog that collects a list of items from the user and returns them as an array?

A. In ECMAScript, arrays are first class objects. For example, consider the following four element array:

var arrOrder = new Array("single","tall", "soy", "latte");

When the typeof unary operator is applied to an array, it returns the string 'object':

typeof arrOrder

Using the for...in construct, you can enumerate each index of the array as you would the properties of an ECMAScript object.

for (var p in arrOrder) {
print(p + "=" + arrOrder[p]);
}

Assuming the print function outputs its arguments to the log or the console depending upon your environment, you would see the following:

0=single
1=tall
2=soy
3=latte

Section 15.4 of the ECMA-262 specification has all the gory details about Array objects in ECMAScript.

Since the indices of an ECMAScript Array can be treated as object properties,
we devise a general solution that works for arrays and for objects.

The following user-defined ECMAScript function serializes a given object into a string of name/value pairs delimited by semi-colons. You can then submit a variable that references the string to a document server.


function Serialize(aObj, aPrefix, aPairs, aVisited) {
   var bTop = false;
   if ('object' != typeof aPairs) {
     Serialize.MARKER = 'XXXserializedXXX';
     aPairs = new Array(); // name/value pairs
     aVisited = new Array(); // array of objects we've visited
     bTop = true;
   }
   aObj[Serialize.MARKER] = 1;
   aVisited.push(aObj);

   for (var p in aObj) {
     var val = aObj[p];
     
     if ('object' == typeof val && val != null) {
       if (val[Serialize.MARKER]) {
         // Skipping self-referencing object
       }
       else {
         Serialize(val, aPrefix + '.' + p, aPairs, aVisited);
       }
     }
     else if (p != Serialize.MARKER) {
       aPairs.push(aPrefix + '.' + p + '=' +  aObj[p]);
     }
   }
   if (bTop) {
     // walk the visited list and nuke
     for (var iVisited = 0; iVisited < aVisited.length; iVisited++) {
       delete(aVisited[iVisited][Serialize.MARKER]);
     }
     return aPairs.join(';');
   }
 }
 
 

You only pass the first two parameters to the function - a reference to the object you wish to serialize (aObj), and a string prefix used to name the top-level object (aPrefix). The function uses the for..in construct to iterate through the enumerable properties of the object. If a property of the object is itself an object reference, the function first verifies that the object has not yet been visited by checking for a special property "XXXserializedXXX". If the property is not found, the function sets this property on the object and then calls itself with the new object as the first argument. Eventually, after all properties have been enumerated and added to the aPairs array with their respective values, the function generates a string in the following format:

prop-1=val-1;prop-2=val-2;...;prop-n=val-n

As part of the final step, the special property introduced by the function, "XXXserializedXXX", is removed from all objects stored in the aVisited array.

Consider the following object:


var objPerson = {'fname' : 'joe', 'lname' : 'smith',
        'phone' : '8005551212', 
        'address' : {'street' : '1310 villa street',
              'city' : 'mountain view',
              'zip' : '94041'}};
 

Calling Serialize(objPerson, "person") returns the following string (carriage returns and line feeds added for readability):

"person.fname=joe;person.lname=smith;person.phone=8005551212;
person.address.street=1310 villa street;person.address.city=mountain view;
person.address.zip=94041"

Recalling our coffee drink array above, calling Serialize(arrOrder, "order") results in the following string:

"order.0=single;order.1=tall;order.2=soy;order.3=latte"

If there's a chance that one of the objects you'll serialize contains a property named "XXXserializedXXX",
you can either:

1) Change the value of the Serialize.MARKER property, or
2) Eliminate the marker and instead search the aVisited array for a matching object reference.

Searching the array for a matching object reference, a linear search, will result in a performance penalty, but the penalty is negligable if the number of object properties in the object to be serialized is small. In fact, if the object you wish to serialize doesn't contain any object properties, you can eliminate setting and checking for the marker and the aVisited array altogether.

Once your object is serialized, you're ready to submit it to a Web server for further processing

<var name="blob" expr="Serialize(objPerson, 'person')"/>
<submit next="validate_person.cgi" namelist="blob" />

It's up to your back-end programmer to parse the string on the server. Here's some sample code written in Perl. It takes a string in the format produced by the Serialize function above and inserts the name/value pairs into a Perl hash or associative array.


sub Deserialize {
  my ($str) = @_;

  my $hObj = {};
  # assumes no value contains a ';'
  my @pairs = split /;/, $str;
  foreach my $pair (@pairs) {
    # assumes no value contains an '='
    my @nv = split /=/, $pair;
    if (@nv != 2) {
      print STDERR "split on '=' expected 2 values\n";
      next;
    }
    $hObj->{$nv[0]} = $nv[1];
  }
  $hObj;
}
 


back to the top

Copyright © 2001-2004 VoiceXML Forum. All rights reserved.
The VoiceXML Forum is a program of the
IEEE Industry Standards and Technology Organization (IEEE-ISTO).