CallXML 3.0 Development Guide Home  |  Frameset Home

  Introduction to XPath  |  TOC  |  Introduction to Server Side Languages  

Tutorial: CallXML with Xpath

This tutorial is based on the things you accomplished in the previous CallXML 3.0 tutorials. If you haven't yet done those lessons, you'll need to go through them first, else you may well find that the fundamental CallXML 3.0 concepts covered here very difficult to grasp.

Note: This tutorial requires the use of outbound dialing priveleges, which must be provisioned by voxeo support on a per-account basis. If you have not contacted us to get these permissions, click here to learn how you can get this feature enabled.

In this Lesson, we will:

What is Xpath?

One of the most powerful additions to the new CallXML 3.0 markup is the inclusion of Xpath support. Never heard of Xpath? Join the club; as Xpath is relatively new, there aren't a heck of a lot of people who are intimately familiar with it. However, we hope to change that. From the W3C specification, "Xpath is a language for addressing parts of an XML document, designed to be used by both XSLT, and XPointer". What this means to us IVR developers, is that we now have a simple, (note: much simpler than DOM!), means of fetching data from an external XML, or text-based source, and importing it to the CallXML 3.0 context. Xpath also has a number of built-in function calls that allow us to further manipulate our data in ways that were not available with previous versions of CallXML, thereby extending the features that are available to us when working with purely static documents: Before the inclusion of XPath into the CallXML 3.0 markup, we had to resort to dynamic markup to perform mathematical operations, string concatenation, character replacements, and the like. For example, an RSS newsfeed via-phone application is now not only possible, but easy as pie, and involves no server side backend at all to make it happen! Anyone who has been working with CallXML over the years will immediately see how useful these features are, and to those new to IVR development will be delighted to see just how easy it is to write complex applications with a minimum of effort and experience. For a definitive listing of the available functions, we invite you to visit our CallXML3.0 XPath documentation.


Step 1: creating our XML data document

As we hinted at earlier, our auto attendant application is going to fetch an XML document, and then use Xpath to import this data into our CallXML context. In this case, we will want a listing of names, phone numbers, and departments for our employee roster. As we often like to get fancy, we will add some additional information such as sex, and address type,(home/cell/office).
While you will likely wish to remove, add, or otherwise change the data structure for your own implementation of this application, our sample xml file will be in the following format, with the highlighted data types being used:


<?xml version="1.0" encoding="UTF-8" ?>
    <contacts>

        <contact type="person||group" sex="male||female" name="Firstname Lastname" alterego="Public Persona" dept="Department Name">
           
1112223333

        </contact>

    </contacts>
</xml>



Nothing really fancy to it, right? We have a plain-vanilla XML document structure, (notice the header), with a series of nested tags that contain aptly-named attributes, and their associated values. We also nest some content within the
node, just so we can show off, and illustrate how we can pick out node values, and attribute values. The base template that we will use is printed below, which will look pretty basic to most of us who have worked with XML in any capacity, and suspiciously familiar to rabid comic book nerds. In our XML document, we define a list of costume-wearing employees for The Watchmen Corporation, each of which has an entry for name, department, and phone number in our data structure. We don't bother accessing our other defined attribute values in this tutorial, as they are really just for show...but those developers eager to hone their skills are encouraged to give it a try to improve their understanding of XPath.


As such, our two-entry sample XML file, (contacts.xml), is going to look like this:

<?xml version="1.0" encoding="UTF-8" ?>
   
       
           
4079990000

           
4072223333

        </contact>

        <contact type="person" sex="male" name="Jon Osterman" alterego="Doctor Manhattan" dept="Special Talents">
           
4071112222

        </contact>

        <contact type="person" sex="male" name="Walter Kovacs" alterego="Rorschach" dept="Vigilantism">
           
4074445555

           
4075556666

           
4076667777

        </contact>

        <contact type="person" sex="male" name="Dan Dreiberg" alterego="Nite Owl" dept="Retirement">
           
4078889999

        </contact>

        <contact type="person" sex="male" name="Adrian Veidt" alterego="Ozmandias" dept="Global Domination">
           
4074446666

        </contact>

        <contact type="person" sex="female" name="Laurie Juspeczyk" alterego="Silk Spectre" dept="Unemployment">
           
4078884444

        </contact>
    </contacts>



Step 2: creating our initial CallXML structure

To get our "virtual Receptionist" application started, we will begin by creating a new document that contains the usual <xml> and <callxml> declarations, but we are also going to add the <fetch> of the XML document right at the very start. This sensibly named element is what we invoke to grab data from an external source, (our "contacts.xml" file), so we can then store the information within the 'var' attribute, and then use XPath expressions to manipulate it based on caller interaction with the application.

Once we invoke our XML document via <fetch>, the entire contents of the XML document are stored in the 'var' attribute, so this is where the XPath comes into play. We are going to use the list function to pull everything from the 'name' and 'dept' attributes of the 'contact' elements and assign it to callxml variables. Then, the fun begins:



<?xml version="1.0" encoding="UTF-8" ?>
<callxml version="3.0">

    <fetch var="myContacts" value="contacts.xml" type="xml"/>

    <assign var="contactChoices" expr="list(/var/myContacts/contact/@name)"/>
    <assign var="deptChoices" expr="list(/var/myContacts/contact/@dept)"/>

    <log>*** CONTACT CHOICES: $contactChoices; ***</log>
    <log>*** DEPT CHOICES: $deptChoices; ***</log>


</callxml>


For those of us unfamiliar with Xpath syntax, a quick breakdown on the Xpath syntax that we used in our variable assignation is in order. We will be reusing these same XPath node-addressing concepts throughout the rest of this project, so internalizing this method of Xpath addressing will be very useful later on.



In our callxml 3.0 document, we then output these values via a <log> statement early in the game, just so we can see exactly what we are working with as we watch the debugger logs:



    *** CONTACT CHOICES: Jon Osterman, Walter Kovacs, Dan Dreiberg, Adrain Veidt, Laurie Juspeczyk ***
    *** DEPT CHOICES: Special Talents, Vigilantism, Retirement, Global Domination, Unemployment ***



A fairly straightforward ordered list, yes? Our next step is to present this list to our callers as a voice activated grammar, but we have to give them some context.. Enter the <prompt> tag.


Step 3: Adding our menu structure, and including event handlers

Now that we have an idea on how we are going to grab our data using XPath, we will next add in our user menu that allows our callers to navigate through our application. For this purpose, we will use the new <prompt> element as our primary method of outputting audio. The <prompt> element is a very much improved version of the <playaudio> tag:

<prompt value="alpha, bravo, charlie">

In this snippet, the following files would be played, in sequence, to our callers:

"alpha.wav"
"bravo.wav"
"charlie.wav"

If the above files did not exist, then the CallXML browser would then render the following string using text-to-speech:

"alpha, bravo, charlie"

Within our intro <prompt>, we will import our ordered listing of values as a voice grammar by referencing the variable names that we saved this list to within the 'choices' attribute of <prompt>. We also add an option for 'directory' in the event that our callers want to hear a listing of employees. In such an eventuality, we simply have an <on> event handler that loops through the listing via the 'foreach' attribute, and renders it to the caller. Note how the <prompt> nested within this handler inherits the value of the 'foreach' attribute defined in the parent element, which illustrates a fundamental feature of CallXML 3.0: inheritance of container element values. As <prompt> is not defined with any value at all, it will inherently take on the value of the 'foreach' attribute in the containing<on> element.


<?xml version="1.0" encoding="UTF-8" ?>
<callxml version="3.0">

      <fetch var="myContacts" value="contacts.xml" type="xml"/>


      <assign var="contactChoices" expr="list(/var/myContacts/contact/@name)"/>
      <assign var="deptChoices" expr="list(/var/myContacts/contact/@dept)"/>

      <log>*** CONTACT CHOICES: $contactChoices; ***</log>
      <log>*** DEPT CHOICES: $deptChoices; ***</log>


        <prompt value="hello and welcome to the Watchmen Corporation"/>

        <prompt label="main" repeat="4" maxtime="8s"
            value="say the name of the person or department you want to call" choices="$contactChoices;, $deptChoices;, directory"/>


            <on event="maxtime" next="#main">
                <prompt value="sorry i didnt hear anything"/>
            </on>

            <on event="choice:nomatch" next="#main">
                <prompt value="im sorry, i didn't get that"/>
            </on>

            <on event="choice:directory" foreach="you can say any of the following:, $contactChoices;" next="#main">
                <prompt/>
            </on>

            <on event="choice" value="$contacts;" next="#contactType">
                <assign var="entity" value="$session.lastchoice;"/>
            </on>

</callxml>



Those of us who are familiar with CallXML 3.0 event handling will notice how we use the modular <on> elements to shunt our callers to different locations in the application flow based on their choice when this menu plays. When a caller actually does choose a valid contact name, or department name, that's where things get a bit more interesting, as we shall soon see.


Step 4: More Xpath, and the usage of conditional logic

In this portion of the application design, we are going to use some shrewd nesting of container elements, which is not a practice to be used arbitrarily in CallXML applications. Be aware that when employing such document design, you must take great care when adding event handlers. Doing so out of the proper scope can cause some really unexpected behavior in your call flow, and event handling. Word to the Wise, folks.

In this case, we will declare <do label="contactType"> as our "parent" container, and perform some conditional logic based on the user's contact match to our voice grammar. All this fancy logic is in place to disambiguate whether or not the match was a person's name, or a department name. What we are going to do in this case, is to compare the utterance with the the values defined in our $contactChoices; and $deptChoices; variables to see which list value matches the utterance. How the heck do we do that? We have a couple of steps here, but lets take them one by one.

The first thing we will do is define the 'personOrDept' variable within the first container nested in the "contactType" container: This "contactChoices" variable will hold the values of the node data that we gathered that lists out all contact names, remember:


Jon Osterman, Walter Kovacs, Dan Dreiberg, Adrian Veidt, Laurie Juspeczyk, Edward Blake



The first place where this comes into play is within our third nested container element, where we loop through all these values one by one to see if we get a match:


  <do foreach="$contactChoices;" var="personOrDept">
  <if value="$session.lastchoice;" value-is="$personOrDept;">
    ..


Here, we invoke the 'value-is' attribute, which is one way of doing conditional logic within CallXML 3.0. What this attribute does in this case, is evaluates whether or not the 'value' attribute matches the 'value-is' attribute expression. So, if our caller says "Walter Kovacs", then the second iteration of our "foreach" loop will make this condition evaluate to "true", thereby executing the content of this <if> statement:

<if value="Walter Kovacs" value-is="Walter Kovacs">

If the caller instead speaks "Vigilantism", which is a department name, this <if> statement will evaluate to "false", and we skip down to the next set of <do> and <if> containers, where similar logic applies. As our container element in this case specifies the value of the "department" nodes that we collected:


Special Talents, Vigilantism, Retirement, Global Domination, Unemployment, Government Affairs



  <do foreach="$deptChoices;" var="personOrDept">
    <if value="$session.lastchoice;" value-is="$personOrDept;">
    ..


Within each of these condional container elements, we then assign the contactType as "person" or "department", and do another Xpath lookup, where we grab the amount of "address" nodes for the contact, or department match:


  <assign var="addressCount" expr="count(/var/myContacts/contact[@name='$entity;']/address)"/>
  ..
  <assign var="addressCount" expr="count(/var/myContacts/contact[@dept='$entity;']/address)"/>



For this, our Xpath statement specifies that we use the 'count' function to evaluate the appropriate statement to see just how many
nodes a given contact match has. Note that this is different for each type of match, (person, or department), we have, and that our Xpath statement specifies a different condition to evaluate against. If there is only a single match, we can go ahead and place the call after we assign the single contact number value to the "number" variable:







And here we whip out the 'test' attribute, which is really very similar to the 'value-is' attribute we used above. In this case, if the 'addressCount' variable value is equal to '1' then we assign the 'number' variable the value of our Xpath lookup. If the statement is *not* true, then the 'number' variable isn't assigned a value at all, and we skip down to the next line of CallXML 3.0 executable content. In this case, it is our elemenet, which will still need to have the same conditional logic by way of the 'test' attribute. If we omitted this attribute, then the call flow would skip down to the 'call' container immediately, even if we had more than one addressCount, which would result in our 'number' variable, (ie the phone number to dial), never being defined....that would be Bad.





Assuming that we do have a match that has more than one possible address type, (addressCount is greater than '1'). If we have more than one possible address, then we will want to ask the caller which number, (home, office, or cell), that they want to call. For now, we simply list out the comma-delimited values to the "addressChoices" variable so that we can prompt our caller when the time is ripe:



 
    ..
 



Note the "!=", or "not equal to" operator in our 'test' attribute value: This is fairly self-evident, but these statements will only be executed when our "count" is not equal to '1', right? Since we have covered a good deal of semi-complex XPath and conditional logic statements, let's see what all these inclusions will look like in our code:



     

           

             

               
               

             
               

               
             
                                    expr="list(/var/myContacts/contact[@name='$entity;']/address/@name )" test="$addressCount; != 1"/>
             

           


             
             
               
               

             
               

               

             
                                    expr="list(/var/myContacts/contact[@dept='$entity;']/address/@name )" test="$addressCount; != 1"/>

             

             


       




Step 5:Finalizing the contact destination with more Xpath

Now that we have all of our conditional logic in place to handle the various XPath matches that we can get, we now need to look at how we can present these to our callers so that they can make a choice: If a contact has a home, office, and cell number, we should let the caller pick and choose exactly which contact number to call, right? As we covered quite a bit of ground in our last step, let's revisit the coding that we did that is relevant to this step in our project. In our previous step, one of the last things that we did was an Xpath lookup, we gathered all 'type' attribute values that were defined in the 'address' node(s) for the contact that was chosen, and assigned the comma-delimited listing to the 'addressChoices' variable. For the sample utterance of 'Walter Kovacs', we took the values highlighted below:


       
           
4074445555

           
3212021726

           
4076667777

        </contact>


...and then assigned them to our variable via the Xpath lookup:


<assign var="addressChoices" expr="list(/var/myContacts/contact[@name='$entity;']/address/@name )" test="$addressCount; != 1"/>


..which in turn, means that variable name 'addressChoices' now evaluates to:


office, home, cell


All clear? Swell. Now that we are all on the same page, we can then look at how we can output our values to the caller when there is more than one address choice. For this stage of the app, we will present a rather brief listing of choices where we list out our 'addressChoices' values, along with an option to cancel the call, which we do by defining the appropriate values within the <prompt value> attribute. We duplicate these definitions within the "choice" attribute, so that our callers can actually input the values when prompted:



  <prompt label="addressMenu" repeat="3"  value="$addressChoices;, or, cancel"  choices="$addressChoices;, cancel"  maxtime="8s" next="#main" test="$addressCount; != 1">
  ..
  </prompt>


We want to be sure that this prompt is *only* executed when we have more than one contact address possibility, so we again invoke our old friend the 'test' attribute, and specify our condition accordingly. In the event that we do not get any input at all, we elegantly shunt the caller's back to the start of the application via the 'next' attribute. Now that our caller instructions are in place, we can then add in some actions based on the callers response to this input field:



  <prompt label="addressMenu"
                    repeat="3"
                    value="$addressChoices;, or, cancel"
                    choices="$addressChoices;, cancel"
                    maxtime="8s"
                    next="#main" test="$addressCount; != 1">

                    <on event="maxtime">
                        <prompt value="sorry i didnt hear anything"/>
                    </on>

                    <on event="choice:nomatch">
                        <prompt value="im sorry, i didn't get that"/>
                    </on>

                    <on event="choice:cancel" next="#main"/>

                    <on event="choice" next="#call">
                        <prompt value="i heard you say, $session.lastchoice;. i will now connect you with, $entity;"/>

                    <!-- dept -->
                        <assign var="number"
                            expr="/var/myContacts/contact[@dept='$entity;']/address[@name='$session.lastchoice;']/text()"
                            value="$contacts;" test="$entityType; = 'dept'"/>

                    <!-- person -->
                        <assign var="number"
                            expr="/var/myContacts/contact[@name='$entity;']/address[@name='$session.lastchoice;']/text()"
                            value="$contacts;" test="$entityType; = 'contact'"/>

                    </on>

                </prompt>



Most of this logic should be pretty familiar to you by now, as these core concepts have been thoroughly detailed in previous tutorials. As such, we will focus our efforts on what happens when we receive an utterance match that defines one of the values contained in the 'addressChoices' variable. The first thing we will need to do is declare a new variable called 'entity' that uses 'session.lastchoice' as our value. As you may have intuited, this populates our new variable with the value of caller's last contact choice.  When this occurs, we again employ some conditional logic based on the 'entityType' variable that we defined earlier: If the value is 'person', then we do an Xpath lookup based on the input value, (again, leveraging the 'test' attribute), and grab out the proper contact number from the <address> node that matches the 'home, office, cell' listing defined in our grammar choices above. All that remains now, is to place our call...


Step 6: Finally placing the call

Home stretch, folks. The last remaining tidbit is to add in the code that does the outbound transfer. Again, if you are using CallXML 3.0 on the Voxeo hosted platform, you will need outbound dialing rights in order to make this work! This last step is the simplest of all: we just add us in another container element that holds a <transfer> tag. The 'value' attribute of this tag defines what number we are going to dial, and we can populate this with our "number" variable that we just defined in Step 5:



        <do label="call" next="#main">
            <transfer type="bridged" value="$number;"/>
        </do>


And that's all there is to it! For clarity, let's take a look at the complete set of CallXML 3.0 code so we can get an idea as to the Big Picture:


<?xml version="1.0" encoding="UTF-8" ?>
<callxml version="3.0">

        <fetch var="myContacts" value="contacts.xml" type="xml"/>

      <assign var="contactChoices" expr="list(/var/myContacts/contact/@name)"/>
      <assign var="deptChoices" expr="list(/var/myContacts/contact/@dept)"/>

        <prompt value="hello and welcome to the Watchmen Corporation"/>

        <prompt label="main" repeat="4" maxtime="8s"
            value="say the name of the person or department you want to call" choices="$contactChoices;, $deptChoices;, directory"/>

            <on event="maxtime" next="#main">
                <prompt value="sorry i didnt hear anything"/>
            </on>

            <on event="choice:nomatch" next="#main">
                <prompt value="im sorry, i didn't get that"/>
            </on>

            <on event="choice:directory" foreach="you can say any of the following:, $contactChoices;" next="#main">
                <prompt/>
            </on>

            <on event="choice" value="$contacts;" next="#contactType">
                <assign var="entity" value="$session.lastchoice;"/>
            </on>

      <do label="contactType">

            <do foreach="$contactChoices;" var="personOrDept">

                <log>*** contact choices =  $contactChoices;</log>
                <log>*** personorDept = $personOrDept;</log>

              <if value="$session.lastchoice;" value-is="$personOrDept;">

                <assign var="entityType" value="'contact'"/>
                <assign var="addressCount" expr="count(/var/myContacts/contact[@name='$entity;']/address)"/>

              <!-- address count = 1 -->
                <assign var="number" expr="/var/myContacts/contact[@name='$entity;']/address/text()" test="$addressCount; = 1"/>

              <!-- address count NEQ 1 -->
                <assign var="addressChoices"
                    expr="list(/var/myContacts/contact[@name='$entity;']/address/@name )" test="$addressCount; != 1"/>

              </if>
            </do>

              <do foreach="$deptChoices;" var="personOrDept">
                <log>*** dept choices = $deptChoices;</log>
                <log>*** personorDept = $personOrDept;</log>

              <if value="$session.lastchoice;" value-is="$personOrDept;">
                <assign var="entityType" value="'dept'"/>
                <assign var="addressCount" expr="count(/var/myContacts/contact[@dept='$entity;']/address)"/>

              <!-- address count = 1 -->
                <assign var="number" expr="/var/myContacts/contact[@dept='$entity;']/address/text()" test="$addressCount; = 1"/>

              <!-- address count NEQ 1 -->
                <assign var="addressChoices"
                    expr="list(/var/myContacts/contact[@dept='$entity;']/address/@name )" test="$addressCount; != 1"/>

              </if>
              </do>

                <prompt label="addressMenu"
                    repeat="3"
                    value="$addressChoices;, or, cancel"
                    choices="$addressChoices;, cancel"
                    maxtime="8s"
                    next="#main" test="$addressCount; != 1">

                    <on event="maxtime">
                        <prompt value="sorry i didnt hear anything"/>
                    </on>

                    <on event="choice:nomatch">
                        <prompt value="im sorry, i didn't get that"/>
                    </on>

                    <on event="choice:cancel" next="#main"/>

                    <on event="choice" next="#call">
                        <prompt value="i heard you say, $session.lastchoice;. i will now connect you with, $entity;"/>

                    <!-- dept -->
                        <assign var="number"
                            expr="/var/myContacts/contact[@dept='$entity;']/address[@name='$session.lastchoice;']/text()"
                            value="$contacts;" test="$entityType; = 'dept'"/>

                    <!-- person -->
                        <assign var="number"
                            expr="/var/myContacts/contact[@name='$entity;']/address[@name='$session.lastchoice;']/text()"
                            value="$contacts;" test="$entityType; = 'contact'"/>

                    </on>
                </prompt>
        </do>

        <do label="call" next="#main">
            <transfer type="bridged" value="$number;"/>
        </do>

    </callxml>





Step 7: Upload, and try it out!

Our Auto Attendant application is now complete. All that remains is for us to upload our files, (AutoAttendant.xml, and contacts.xml), to a webserver, map a callXML 3.0 number to the URL, and give it a call. However, if you have motor control skills that precludes typing this code out for yourself, you can download a zip file with the source code below.


  CallXML 3.0 source code.





  ANNOTATIONS: EXISTING POSTS
0 posts - click the button below to add a note to this page

login
  Introduction to XPath  |  TOC  |  Introduction to Server Side Languages  

© 2012 Voxeo Corporation  |  Voxeo IVR  |  VoiceXML & CCXML IVR Developer Site