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
speak.and.listen@voicexmlreview.org
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;
}
|
|