|
Implementing
VoiceXML (VXML)
How
to implement VoiceXML (VXML) with the ScriptEase ISDK
4.4
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 4.40 for specifics, but in general is application
to the 4.30 and SE:ISDK/Java versions. This document
is also available for the ScriptEase 5.0 API here.
---------- Revision History for this document ----------
Nov 16 2001 - Initial document
Feb 06 2003 - Fix demo typos for scoping
--------------------------------------------------------
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 recommend the use
of global variables), with just two chains of document
scope and only one (anonymous) var 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 */
jseVariable sessVar;
jseVariable appVar;
jseVariable docVar[2];
jseVariable dlgVar[2];
jseVariable anonVar[2];
Each of these variables 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 variable 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, and loaded
all libraries (see LoadLibrary_All()), there
is a default global that has the standard ECMAScript
objects scoped to it. The libraries need to be available
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 reality these scopes and relationships would not
be set up in script code, but would 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 variables and two dialog levels */
void initScopes(jsecontext se)
{
int i;
var globalVar;
/* set up each scope variable and relationship uses */
sessVar = makeScopeVar(se,NULL/*no parent*/,UNISTR("session"));
appVar = makeScopeVar(se,sessVar,UNISTR("application"));
for ( i = 0; i < 2; i++ )
{
docVar[i] = makeScopeVar(se,appVar,UNISTR("document"));
dlgVar[i] = makeScopeVar(se,docVar[i],UNISTR("dialog"));
anonVar[i] = makeScopeVar(se,dlgVar[i],NULL);
}
/* set up appVar to refer to itself also as document */
assignMemberToObject(se,appVar,UNISTR("document"),appVar);
/* make session variable inherit from global via prototype */
globalVar = jseGlobalObjectEx(se,jseCreateVar);
assignMemberToObject(se,sessVar,UNISTR("_prototype"),globalVar);
jseDestroyVariable(se,globalVar); /* C lock no longer needed */
}
/* helper function to assign an object member to another object */
void assignMemberToObject(jsecontext se,jseVariable thisObject,
const jsecharptr name,jseVariable assignTo)
{
/* equivalent to thisObject.name = assignTo */
jseVariable memberVar;
memberVar = jseMemberEx(se,thisObject,name,jseTypeUndefined,jseCreateVar);
jseAssign(se,memberVar,assignTo);
jseDestroyVariable(se,memberVar); /* remove C lock on new variable */
}
/* helper function to do standard VXML object create and scope */
jseVariable makeScopeVar(jsecontext se,jseVariable parentObj,const jsecharptr name)
{
/* v = new Object(); v.__parent__ = parentObj; v.name = v */
jseVariable selfObj;
selfObj = jseCreateVariable(se,jseTypeObject);
if ( name != NULL )
/* equivalent to selfObj.name = selfObj */
assignMemberToObject(se,selfObj,name,selfObj);
if ( parentObj != NULL )
/* equivalent to selfObj.__parent__ = parentObj; */
assignMemberToObject(se,selfObj,UNISTR("__parent__"),parentObj);
return selfObj;
}
Executing Scripts
When it is time to execute scripts, you will pass the
script code into the jseInterpret() function (or possible
jseInterpExec() which will be covered later). It's also
possible to precompile scripts into a function, and
call the function directly with jseCallFunction(), but
the jseInterpret() approach is the easiest way to begin.
When it is time to execute script code, the jseInterpret()
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
jseInterpret() 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 1
#define JSE_ALWAYS_IMPLICIT_PARENTS 1
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).
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 */
jseSetGlobalObject(se,dlgVar[1]);
/* call jseInterpret on that script */
jseInterpretEx(se,NULL,script,NULL,
jseNewNone, /* no changes to the context */
JSE_INTERPET_IMPLICIT_THIS|JSE_INTERPRET_IMPLICIT_PARENTS,
NULL,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.
|