VoiceExcerpt: Early Adopter VoiceXML: VoiceXML with XSLT (HTML and WML), Part 3

Excerpt: Early Adopter VoiceXML: VoiceXML with XSLT (HTML and WML), Part 3

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Wrox Press Book – Early Adopter VoiceXML


Chapter 7, VoiceXML with XSLT (HTML and WML), Part3


WROX Press' New VoiceXML book
In this chapter, all the grammars are inline, but XSLT could just as easily be used to create standalone external grammars in separate files, or to generate grammars in multiple formats (GSL, JSGF, XML) from a common data source, by the application of different stylesheets.


Just as we iterated over the order_history/order node-set to create grammars with options for all the orders of a customer, we must now iterate over these nodes again to create individual forms containing the details for each order.


<xsl:for-each select=”order_history/order”>
order number
<xsl:value-of select=”position()”/> |
</xsl:for-each>
buy me
</grammar>
<filled>
<assign name=”form_pointer”
expr=”‘productList'”/>
<assign name=”user_command”
expr=”userSaid”/>
<goto next=”#navigator”/>
</filled>
</field>
</form>

If there were consistently a very large number of orders per user, we might look at generating the order details on the fly using JSP or PHP to create a separate VoiceXML document.


<xsl:for-each select=”order_history/order”>
<xsl:variable name=”order_detail_counter”
select=”position()”/>
<form id=”order_{$order_detail_counter}”>

The block above illustrates the other way XSLT offers for including dynamic content in an attribute value, this time through the <xsl:variable> element, the value of which can be output by preceding the identifier with the dollar sign, $. This technique is used several times when creating the VoiceXML forms for each order.

<noinput>
<!– This falls through to order status top level –>
<assign name=”user_command” expr=”‘order status'”/>
<goto next=”#navigator”/>
</noinput>
<nomatch>
<goto next=”#errorHandler”/>
</nomatch>
<block>
<assign name=”form_pointer”
expr=”‘order_{$order_detail_counter}'”/>
</block>
<field name=”userSaid”>

The following code generates the prompt to announce the order detail:


<prompt bargein=”true” timeout=”1s”>
This order was placed on <sayas class=”date”>
<xsl:value-of select=
“order_date/@sayas”/></sayas>.
<break msecs=”500″/>
The order consisted of
<xsl:for-each select=”product”>
quantity
<xsl:value-of select=”./@quantity”/>
of product

The <xsl:value-of> element below retrieves the name of the product with an XPath expression that selects products from the XML product_list section where the id attribute matches the id attribute of the current order. This is analogous to an SQL join between the customer_order and customer_order_product tables from relational schema. Note that XPath denotes an attribute with use of the at symbol, @.

<xsl:value-of select=
“/myrubberbands/product_list/product[@id=current()/@id]”
/>
<break msecs=”500″/>
</xsl:for-each>
The total of the order was

Next, we see the VoiceXML <sayas> element put to use. Whether or not this causes the contents to be rendered correctly as currency depends on the TTS engine used, and its support for pronunciation markup. The rest of the block repeats the previous technique to add order options to the inline <grammar> element.

<sayas class=”currency”>
$<xsl:value-of select=”total_charge”/>
</sayas>.
The status of this order is
<xsl:value-of select=”order_status”/>.
</prompt>
<grammar type=”application/x-jsgf”>
list |
product list |
more information |
frequently asked questions |
questions |
order status |
<xsl:for-each select=
“/myrubberbands/customer_record/order_history/order”>
order number
<xsl:value-of select=”position()”/> |
</xsl:for-each>
buy me
</grammar>
<filled>
<assign name=”form_pointer” expr=”‘productList'”/>
<assign name=”user_command” expr=”userSaid”/>
<goto next=”#navigator”/>
</filled>
</field>
</form>
</xsl:for-each>

At this point we’ll skip over the product list option, because it doesn’t illustrate anything we haven’t already seen. However, the product listing prompt offers the user the option to say, “Buy me”, in which case their call is transferred to the MyRubberbands.com’s telephone service center by the placeOrder form below. This is the quickest way for the company to add some commerce capability to the voice system, but note that the VoiceXML <transfer> element is not implemented by all platforms. When the user returns from the call, they are unconditionally sent to the main menu by the subsequent <goto> element.


<form id=”placeOrder”>
<block>
<prompt bargein=”false” timeout=”1s”>
Transferring your call to customer service.
</prompt>
</block>
<transfer name=”callSales” dest=”MYRUBBERBND”
connecttimeout=”30s” bridge=”true”/>
<block>
<goto next=”#mainMenu”/>
</block>
</form>

We can also skip over the frequently asked questions option, since it contains only static VoiceXML code. We are now almost at the end of our stylesheet, where the navigator form is located, holding the navigation logic for many of the state transitions in our interface design.


It consists simply of an if-else construct that expresses the state transitions on the diagram of the user interface earlier. If more commands, states, or transitions were to be added to the design, the complexity of the interface might exceed the limitations of this method of implementation.


<form id=”navigator”>
<block>
<if cond=”user_command == ‘product list’ ||
user_command == ‘list'”>
<goto next=”#productList”/>
<elseif cond=”user_command == ‘questions’ ||
user_command == ‘frequently asked questions’ ||
user_command == ‘more information'”/>
<goto next=”#FAQ”/>
<elseif cond=”user_command == ‘order status'”/>
<goto next=”#orderStatus”/>
<elseif cond=”user_command == ‘buy me'”/>
<goto next=”#placeOrder”/>

The stylesheet drives the rest of the navigation engine by creating forms for each individual order in this customer’s order history. It does this by means of the following <xsl:for-each> construct:

<xsl:for-each select=”order_history/order”>
<xsl:variable name=”order_counter” select=”position()”/>
<elseif cond=”user_command ==
‘order number {$order_counter}'”/>
<goto next=”#order_{$order_counter}”/>
</xsl:for-each>
<else/>
<goto next=”#mainMenu”/>
</if>
</block>
</form>

Finally, we come to the errorHandler form that is used by most of the handlers in the dialog. This error handler keeps a running count of the number of errors, and when four errors have occurred, it will apologize and disconnect the user. This isn’t the friendliest way of handling errors, and is not suitable for a long-range solution that would more likely transfer the user to a human operator at this point. Not only that, but we’d probably want more sophisticated logic for processing errors, maybe using ECMAScript to vary the response according to the time elapsed since the last error.



<form id=”errorHandler”>
<block>
<assign name=”session_error_count”
expr=”session_error_count + 1″/>
<if cond=”session_error_count &lt; 4″>
<prompt bargein=”false” timeout=”0.1s”>
I’m sorry, but I’m unable to understand you.
</prompt>
<if cond=”session_error_count &gt; 2″>
<prompt bargein=”false” timeout=”0.1s”>
It seems I am having trouble.
</prompt>
</if>
<goto next=”#navigator”/>
<else/>
<prompt bargein=”false” timeout=”0.1s”>
I’m sorry, but I’m having a lot of difficulty
understanding you. If you are currently in a noisy
environment, please call back later.
</prompt>
<exit/>
</if>
</block>
</form>

All that needs to be done now is to close the document, after including an empty template matching standalone <product_list> elements, to suppress any output from them. Without this, default XSLT templates would be applied that output text children of any elements that aren’t explicitly matched by a template already.

</vxml>
</xsl:template>
<xsl:template match=”product_list”/>
</xsl:stylesheet>

We’ve now reached the end of our VoiceXML stylesheet.


Running the Stylesheet


Now it is time to run the stylesheet transform, and produce a complete VoiceXML document for one of our users. If you are using Saxon, enter the following command at the command prompt:


C:> saxon customer_1.xml myrubberbands2vxml.xsl > customer_1.vxml


We are now ready for our VoiceXML interface to go live. This simply requires us to upload the result of our XSLT transformation to our chosen Voice gateway.

Summary

Perhaps the greatest advantage to implementing an XSLT-based system such as this is that documents in other markup languages can be readily generated from the same XML source data. The complete version of this chapter goes on to demonstrate how easy it would be to use MyRubberBandsML with an appropriate XSLT stylesheet to produce WML and HTML interfaces in addition to the above VoiceXML code.


The full chapter explores the process of using XSLT to provide user multiple interfaces to a legacy database that does not provide native XML access to data. Starting with a set of requirements for a voice interface taken from our business needs, an XML format to mark up the legacy data is created, along with an associated XML Schema to document it. We then create XSL templates to automatically generate our VoiceXML interface, and run this VoiceXML inside a simulator, before moving on to look at stylesheets that automatically produce parallel WML and HTML interfaces.


This book excerpt comes to us from WROX Press–technical books that you can count on!

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories