Nombas > SE:ISDK DevSpace > Tips > Implementing VoiceXML (VXML)

 

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.

Home | Scripting | Products | Purchase | Download | Support | Company

Copyright ©2001, Nombas, Inc. All Rights Reserved.
Questions? Visit
http://support.nombas.com/