|
Implementing
VoiceXML (VXML)
How
to implement VoiceXML (VXML) with the ScriptEase ISDK
5.0
This document provides a guide to implementing the
rules of ECMAScript (JavaScript) as applied to the VXML
specification using the ScriptEase ISDK.
ECMAScript (aka JavaScript) is the the required scripting
language for VoiceXML, as defined by the W3C standards
body (see VoiceXML
Forum and VoiceXML
Version 2.0). The recommendations in this document
are based primarily on implementing VoiceXML
Version 2.0 section 5, which is the section about
"Control flow and scripting". This document
is correct insofar as that document is correct. If that
document changes then please inform
Nombas.
This document is based on helping many ISDK users
implement VXML solutions. This is not a complete document,
but only covers those special issues pertaining to VXML
that would not be contained in the standard ScriptEase
ISDK User Manuals. This document refers to ScriptEase:ISDK/C
version 5.00 API, for VXML with the 4.40 API see here.
---------- Revision History for this document ----------
Nov 16 2001 - Initial document
Feb 06 2003 - Fix demo typos for scoping
Apr 08 2003 - Update to version 5 API
--------------------------------------------------------
Variable Scoping
Initialization
This is the VoiceXML Scope hierarchy, as defined in
VoiceXML
Version 2.0 section 5.
session.application.document.dialog.(anonymous).(anonymous)....
.document.dialog.(anonymous).(anonymous)....
.document.dialog.(anonymous).(anonymous)....
.document.dialog.(anonymous).(anonymous)....
- session is the global value setup up by
your application
- application (also referred to as the root document)
is initialized with the session, or loaded by any
document with the application attribute
of the vxml tag.
- document is the scope associated with each
<vxml> tag
- dialog is the scope associated within each
<form> or <menu> tag.
- (anonymous) refers to the unnammed scope
that applies to each <block>, <filled>,
or <blah> tag. There may be any level
of (anonymous) scopes.
There are multiple trees starting at the document
scope because a VoiceXML application typically consists
of multiple documents that are switched. (A typical
VoiceXML server may consist of many connections running
simultaneously, which may be a separate issue from multiple
document scopes within one application
scope. More about such multitasking will be mention
later in this document.)
For the purposes of this document, we will assume that
the implementation is in the C language, and that there
are global variables in the C code representing each
of these scopes (although we don't necessarily recommend
the use of global variables), with just two chains of
document scope and only one (anonymous)
object in each chain, although their may be just 1 or
n. These C variables may look like this:
/* C code showing C representation of the scope variables */
seobject sessObj;
seobject appObj;
seobject docObj[2];
seobject dlgObj[2];
seobject anonObj[2];
Each of these objects will be represented, to both
the C code and the ECMAScript (JavaScript) code, as
an object with the names you'd expect ("session",
"application", "document", and "dialog").
A number of rules withing the VoiceXML specification
refer to the relationship and naming of these scopes.
In summary, the pertinent rules are these:
- All variables belong to the scope in which they
are declared, whether by a <var tag or with
the var keyword in a <script tag.
- Each named scope has a pre-defined property object
whose name is the same as the scope itself, and that
refers to the scope variable. So, for example, if
there is a variable foo in the dialog
scope, and dialog is the current scope, then
that variable can be referred to as either foo
or dialog.foo.
- Each scope inherits from its parent scope. So, for
example, if there is a variable goo in the
document scope, and dialog is the current
scope, then that variable can be referred to simply
as goo. Or it may specifically be referred
to as document.goo (or application.document.goo,
etc...)
- Within the application, which is also referred
to as the root document, there is a pre-defined
variable document which refers to the application
scope. I.E., if the application scope has a
variable zoo, and application is the
current scope, then that variable may be referred
to as either zoo, application.zoo, or
document.zoo.
- From within any scope, access is available to a
default set of standard functions and object known
as the ECMAScript internal objects (e.g. the Date
object).
The inheritance within each scope from its parent scope
will be handled with the __parent__ property
at each scope level. __parent__ is a special
property of objects which is used to scope variables
whenere the ImplicitParents flag is in effect
(more on how to enable ImplicitParents later).
Note regarding __parent__ vs _prototype:
There has been much discussion about whether it is
more appropriate to manage inheritence through the
__parent__ property or through the _prototype
property. _prototype and __parent__ are
two different approaches which seem at first similar,
but really are not. __prototoype__ is most
like object inheritance in object-oriented languages,
and is used for inheriting properties which can be
altered in the descendant without changing the value
inherited from the ancestor (i.e. the value is read-only
from the ancestor). __parent__ is used purely
for defining a search chain when scoping variable
references. Because the scoping in VoiceXML
changes the "inherited" values directly,
without making a new value in the local scoped object
(as demonstrated in the time-telling example in "VoiceXML
Version 2.0 section 5"), __parent__
is the proper technique for VoiceXML.
The final rule, that the standard ECMAScript libraries
are inherited, could be accomplished in many ways. After
you've initialized the ScriptEase engine with seCreateContext()
there is already a default global that has the standard
ECMAScript objects scoped to it. The libraries need
to be added directly to every scope level. Because the
standard libraries are inherited but are not overwritten,
they should be inherited via the _prototype chain, with
something like "myObj._prototype=global".
This can be applied to every scope level, but because
they all inherit from sesssion we will apply
it only to session.
These rules could be represented in ECMAScript code
by something like this:
// make the variables all exist
var session, application, document, dialog;
session = new Object();
session.application = application = new Object();
application.document = document = new Object();
document.dialog = dialog = new Object()
// make each scope object refer to itself with scope name
session.session = session;
application.application = application;
document.document = document;
dialog.dialog = dialog;
// make each object inherit scope from parent
application.__parent__ = session;
document.__parent__ = application;
dialog.__parent__ = document;
// make application also refer to itself as document
application.document = application;
// make session inherit standard library from the global object
session._prototype = global;
In your application, these scopes and relationships
will most likely not be set up in script code, but will
instead be setup up within your program. Building on
the C code listed earlier, these scopes and relationships
could be initialized with the following C code:
/* C code initializing scope object and two dialog levels */
void initScopes(secontext se)
{
int i;
/* set up each scope variable and relationship uses */
sessObj = makeScopeObj(se,NULL/*no parent*/,UNISTR("session"));
appObj = makeScopeObj(se,sessObj,UNISTR("application"));
for ( i = 0; i < 2; i++ )
{
docObj[i] = makeScopeObj(se,appObj,UNISTR("document"));
dlgObj[i] = makeScopeObj(se,docObj[i],UNISTR("dialog"));
anonObj[i] = makeScopeObj(se,dlgVar[i],NULL);
}
/* set up appObj to refer to itself also as document */
sePutObject(se,appObj,SE_MEM("document"),appObj);
/* make session variable inherit R/O from global via _prototype */
sePutObject(se,sessObj,SE_STOCK(_prototype),SE_GLOBAL);
}
/* helper function to do standard VXML object create and scope */
seobject makeScopeObj(secontext se,seobject parentObj,seconstcharptr name)
{
/* v = new Object(); v.__parent__ = parentObj; v.name = v; return v; */
seobject selfObj = seMakeObject(se);
if ( name != NULL )
/* equivalent to selfObj.name = selfObj */
sePutObject(se,selfObj,SE_UNIMEM(name),selfObj);
if ( parentObj != NULL )
/* equivalent to selfObj.__parent__ = parentObj; */
sePutObject(se,selfObj,SE_STOCK(__parent__),parentObj);
return selfObj;
}
Executing Scripts
When it is time to execute scripts, you will pass the
script code into the seEval() function. (BTW, It's also
possible to precompile scripts into a function, and
call the function directly with seEval(...SE_FUNC...)).
When it is time to execute script code, the seExec()
function will manage all of the rules of ECMAScript,
variable creating, standard objects, error trapping,
and so on, under the scope you provide. This scope is
determined by the value of ImplicitThis and ImplicitParents.
ImplicitThis means that "this.property"
is not necessary to scope the "property" element
of the "this" variable, but instead "this"
is implied. ImplicitParents means that the __parent__
chain of the this variable will be searched,
as described earlier. Normally, these Implicit
attributes apply only to functions, but ScriptEase allows
seEval() to apply these flags to any code being interpreted.
NoteYou may want to be sure to set these attributes
on every every function loaded, but since VoiceXML
uses ImplicitThis and ImplicitParents
throughout, it is much easier to add compile-time
options to your jseopt.h file so they are on
for all function by default.
/* sample lines for jseopt.h for default ImplicitThis and ImplicitParents */
#define JSE_ALWAYS_IMPLICIT_THIS
#define JSE_ALWAYS_IMPLICIT_PARENTS
Now that the relationships between scopes are all set
up, you just need to set the appropriate scope as the
current this and the current global variable
when the interpret is executed. Setting the this
variable to the appropriate scope variable ensures that
the scoping rules are followed. Setting the global
variable ensures that any new variables are created
in that scope object (because the var ECMAScript
keyword, when not used within a function block,
will create a variable in the global object).
The thisand the global variables may each
be set in the seEvalParams structure passed to seEval
(you can also explicit manage scopes with this seEvalParams
structure) but unless your needs are very unique it
is easiest just to set the current global variable
before calling seEval(); this leads to default behavior
with thisbeing the same as your new global.
In this short VXML sample, assume a script to set the
current day of the week (e.g. Monday, Tuesday, etc...)
is to be put into the variable Weekday, at the
dialog scope level. The script being executed
may be within VXML code such as this:
<?xml version="1.0"?>
<vxml version="2.0">
<form>
<script>
var d = new Date();
var days = [ "Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" ];
var Weekday = days[ d.getDay() ];
<block>
<prompt>
The day is <value expr="Weekday"/>.
</prompt>
</block>
</form>
</vxml>
The four-line script could be executed by the following
code.
/* C code to execute script in the dialog[1] scope */
char * script = "var d = new Date();\r\n"
"var days = [ \"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\",
\"Thursday\", \"Friday", \"Saturday\" ];\r\n
"var Weekday = days[ d.getDay() ];";
/* set the global variable to be the dialog[1] scope */
sePutObject(se,SE_GLOBAL,SE_VALUE,dlgObj[1]);
/* call seEval() */
seEval(se,script,SE_TEXT,NULL,NULL,
SE_INIT_IMPLICIT_THIS|SE_INIT_IMPLICIT_PARENTS|\
SE_REPORT_ERRORS|SE_INFREQUENT_CONT,
NULL); /* ignore return parameters for this example */
More?
This document has covered some of the most common questions
about how should one use ECMAScript with VoiceXML. There
are surely many topics missing and I'll cover such topics
as implementors request.
If you have suggestions about what should be added
to this document, more question about VoiceXML scripting
, or ciritiques of this document, visit our Online
Support Site.
|