Not-So-Organic Grocery Store

The "Not-So-Organic Grocery Store" demonstrates how to write a data-driven application using VoiceXML. This application simulates an on-line grocery store that allows you to navigate between departments, hear a list of items in a department, add items to your shopping cart, and simulate a checkout. No cash or credit required!

Try It!
  1. Set your Application URL to http://studio.247-inc.net/library2/code/ex-114/index.vxml
  2. Call the number shown in VXML Tools page
  3. Sign in, and play!

This sample demonstrates several techniques for building a data-driven voice application.

  • Retrieve XML data from an application server using the data element.
  • Access the data using the W3C DOM.
  • Encapsulate data access via user-defined JavaScript classes.
  • Queue prompts dynamically using static VoiceXML.

A big advantage to this approach of building data driven applications is that, as your back-end data changes - new departments, new items, and updated prices, your VoiceXML code need not change. Furthermore, because the Tellme Voice Application Network has a robust HTTP caching infrastructure, the interpreter need only fetch the data each time your application is executed. The VoiceXML code, grammars, audio, and JavaScript can remain in the cache. For more information on caching, see Effective Use of Caching to Boost VoiceXML Application Performance.

The XML data includes information about the virtual store including:

  • The name of the store.
  • The available departments at the store.
  • The items available in each department, including a name and a price.

The formal description of the schema for this data is defined in the following document type definition (DTD):

<!ELEMENT market (name, dept*)>
<!ELEMENT dept (name, item*)>
<!ATTLIST dept id ID #REQUIRED>

<!ELEMENT item (name, price)>
<!ATTLIST item id ID #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT price (#PCDATA)>

Although the Tellme VoiceXML interpreter does not perform runtime validation of XML documents, you should define a DTD or XML schema for all XML data returned by your data servers. Your Quality Assurance (QA) team can use those descriptions to build test tools that submit a battery of HTTP requests to your data servers and validate the returned data. For more information about XML validation, see the XML Primer.

The application uses the data element to request XML data conforming to this DTD from an application server. In the example code below, the server-side script, grocerydata.cgi, is implemented as a Perl CGI, but any application server capable of responding to an HTTP request with XML data can be used (e.g. Java Server Pages, Active Server Pages).

An instance of the data returned by the application server looks like the following:

<?xml version="1.0"?>
<!DOCTYPE market SYSTEM "grocerydata.dtd" []>
<?access-control allow="*"?>
<market>
  <name>Tellme Networks</name>
  <dept id="d1">
    <name>meats</name>
    <item id="i10">
      <name>crocodile liver</name>
      <price>4.50</price>
    </item>
    <item id="i11">
      <name>chicken wings</name>
      <price>2.50</price>
    </item>
    <item id="i12">
      <name>gazelle sausage</name>
      <price>8.50</price>
    </item>
    <item id="i13">
      <name>duck breast</name>
      <price>2.25</price>
    </item>
  </dept>
  <dept id="d2">
    <name>vegetables</name>
    <item id="i20">
      <name>eggplant</name>
      <price>2.00</price>
    </item>
    <item id="i21">
      <name>artichokes</name>
      <price>1.50</price>
    </item>
    <item id="i22">
      <name>tomatoes</name>
      <price>1.00</price>
    </item>
    <item id="i23">
      <name>sweet potatoes</name>
      <price>0.50</price>
    </item>
  </dept>
  <dept id="d3">
    <name>fruits</name>
    <item id="i30">
      <name>fuji apples</name>
      <price>1.25</price>
    </item>
    <item id="i31">
      <name>yummy yummy mangoes</name>
      <price>2.00</price>
    </item>
    <item id="i32">
      <name>strawberries</name>
      <price>2.50</price>
    </item>
    <item id="i33">
      <name>nectarines</name>
      <price>1.00</price>
    </item>
  </dept>
  <dept id="d4">
    <name>desserts</name>
    <item id="i40">
      <name>ben and jerrys</name>
      <price>3.00</price>
    </item>
    <item id="i41">
      <name>figgy pudding</name>
      <price>2.50</price>
    </item>
    <item id="i42">
      <name>chocolate cookies</name>
      <price>1.00</price>
    </item>
    <item id="i43">
      <name>myer lemons</name>
      <price>3.00</price>
    </item>
  </dept>
</market>

Once the data is returned by the server, the VoiceXML interpreter parses it into a read-only subset of the W3C Document Object Model (DOM). Observe that each department and item not only includes a name but also a unique identifier. Using the getElementById method of a DOM Node or Document object, you can use this identifier to retrieve corresponding node in the DOM quickly. The application takes advantage of this capability when the user chooses a specific department or item because the return value from the grammar corresponds directly to the unique identifier of the corresponding department or item in the data. You should also observe that the unique identifier corresponds to the name of the audio file for each department and item.

Note. Audio files for departments and items are not available at this time. When you run the application, you will hear Text-To-Speech (TTS).

For more information on programming the DOM via JavaScript, see the data element tutorial

The code for the grocery store application follows:

<vxml version="2.0">

  <!-- source in the virtual shopping cart -->
  <script src="vcart.js"/>
  <!-- source in the virtual grocery store -->
  <script src="grocerystore.js"/>

  <!-- application/document scoped vars. -->

  <!-- base url to earcons -->
  <var name="sUrlEarcons" expr="'http://naturalsound.svc.tellme.com/common-audio/'"/>
  <!-- a list of the department names -->
  <!-- current department (selected in #choose_dept) -->
  <var name="oCurDept"/>
  <!-- current item (selected in each #choose_item dialog) -->
  <var name="oCurItem"/>
  <!-- customer shopping cart (JS object) -->
  <var name="oCart"/>
  <!-- encapsulate access to store data -->
  <var name="oStore"/>
  <!-- total cost of all the items in the cart (calculated in #checkout) -->
  <var name="total_price"/>
  <!-- array of JS objects encapsulating departments -->
  <var name="aDepts"/>

  <script><![CDATA[
  // Initialize application state
  function Init(domStore) {
     // instantiate a new virtual shopping cart
     oCart = new CVSCart();
     // instantiate/initialize a new virtual store
     oStore = new CStore();
     aDepts = null;
     if (oStore.Init(domStore)) {
       aDepts = oStore.GetDepartments();
     }
     total_price = 0;
     oCurDept = oCurItem = null;
  }

  // build a playable list of items; 
  // each item must expose GetID and GetName methods
  function GetNameList(aItems, oConjunction) {
    var aList = [];
    for (var i = 0; i < aItems.length-1; i++) {
      aList.push({'wav' : aItems[i].GetID() + '.wav', 
        'tts' : aItems[i].GetName()});
    }
    if (oConjunction) {
      aList.push(oConjunction);
    }
    aList.push({'wav' : aItems[aItems.length-1].GetID() + '.wav', 
      'tts' : aItems[aItems.length-1].GetName()});
    return aList;
  }
  ]]></script>

  <!-- when the user wants to head to the register -->
  <link event="oncheckout">
  
  <grammar mode="dtmf"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0">
    <rule id="root_rule" scope="public">
      <item>
        7
      </item>
    </rule>

  </grammar>

  <grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0"
         xml:lang="en-US">
    <rule id="root_rule" scope="public">
      <one-of>
        <item>
          purchase
        </item>
        <item>
          check
          out
        </item>
        <item>
          checkout
        </item>
      </one-of>
    </rule>

  </grammar>

  </link>

  <!-- when the user wants to list the items in their shopping cart -->
  <link event="onlistcart">
  
  <grammar mode="dtmf"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0">
    <rule id="root_rule" scope="public">
      <item>
        9
      </item>
    </rule>

  </grammar>

  <grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0"
         xml:lang="en-US">
    <rule id="root_rule" scope="public">
      <item>
        <item>
          <item repeat="0-1">
            list
          </item>
          <item repeat="0-1">
            my
          </item>
          <item repeat="0-1">
            shopping
          </item>
        </item>
        cart
      </item>
    </rule>

  </grammar>

  </link>

  <!-- when the user wants to return to the store entrance -->
  <link event="onhome">
  
  <grammar mode="dtmf"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0">
    <rule id="root_rule" scope="public">
      <item>
        *
      </item>
    </rule>

  </grammar>

  <grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0"
         xml:lang="en-US">
    <rule id="root_rule" scope="public">
      <one-of>
        <item>
          home
        </item>
        <item>
          welcome
        </item>
      </one-of>
    </rule>

  </grammar>

  </link>

  <!-- when the user wants to enter a different department -->
  <link event="onchoosedept">
  
  <grammar mode="dtmf"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0">
    <rule id="root_rule" scope="public">
      <item>
        8
      </item>
    </rule>

  </grammar>

  <grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0"
         xml:lang="en-US">
    <rule id="root_rule" scope="public">
      <one-of>
        <item>
          change
          department
        </item>
        <item>
          change
        </item>
        <item>
          department
        </item>
      </one-of>
    </rule>

  </grammar>

  </link>

  <!-- when the user wants to discard the items in their cart and start over -->
  <link next="onrestart">
  
  <grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0"
         xml:lang="en-US">
    <rule id="root_rule" scope="public">
      <one-of>
        <item>
          start
          over
        </item>
        <item>
          restart
        </item>
      </one-of>
    </rule>

  </grammar>

  </link>

  <catch event="oncheckout">
    <goto next="#checkout"/>
  </catch>

  <catch event="onlistcart">
    <goto next="#list_cart"/>
  </catch>

  <catch event="onhome">
    <goto next="#welcome"/>
  </catch>

  <catch event="onchoosedept">
    <goto next="#choose_dept"/>
  </catch>
  
  <catch event="onrestart">
    <goto next="#confirm_restart"/>
  </catch>

  <catch event="error.badfetch">
    Sorry. We are experiencing technical difficulties. 
    Try again later.
    <disconnect/>
  </catch>
  
  <catch event="">
    <log>catch-all caught <value expr="_event"/></log>
    Sorry. We are experiencing technical difficulties. 
    Try again later.
    <disconnect/>
  </catch>

 <!-- The VoiceXML interpreter executes this dialog first -->
 <form id="main">
    <block>
      <data name="domStore" src="grocerydata.cgi"/>
      <!-- initialize the user's cart -->
       <script>Init(domStore);</script>
       <goto next="#welcome"/>
    </block>
 </form>

<!-- this is the virtual store entrance -->
<form id="welcome">
 <block>
  <audio src="wiz_logo.wav">
    Welcome to the not quite organic grocery store, brought to you by</audio>
  <prompt><value expr="oStore.GetName()"/></prompt>
  <break time="600ms"/>
  <audio src="wiz_forsale1.wav">
    We have various</audio>
  <var name="aList" expr="GetNameList(aDepts, {'wav' : 'xtrawiz_and.wav', 'tts' : 'and'})"/>
  <prompt>  
    <foreach item="oItem" array="aList">
      <audio expr="oItem.wav"><value expr="oItem.tts"/></audio>
    </foreach>
  </prompt>
  <audio src="wiz_forsale2.wav">for sale today.</audio>
  <break time="600"/>
  <audio src="xtrawiz_tochoose.wav">
    To enter a department, just say it's name.</audio>
  <audio src="wiz_hearlist.wav">You will hear a list of items for sale.  
    To add an item to your shopping cart, say it's name.</audio>
  <break time="600"/>
  <audio src="wiz_hearcheck.wav">
    To check out and purchase what is in your cart, say 'check out'.</audio>
  <break time="600"/>
  <audio src="wiz_hearcart.wav">
    To hear what is in your shopping cart, say 'my cart'</audio>
  <break time="600"/>
  <audio src="wiz_hearbye.wav">
    To leave the store without buying anything, say 'goodbye' or hang up.</audio>
  <break time="600"/>
  <audio src="wiz_hearwelcome.wav">
    To return to this welcome message, say 'welcome' at any time.</audio>
  <break time="600"/>
  <goto next="#choose_dept"/>  
  </block>
</form>

<!--
User chooses a department here. 
A department index variable is set, 
and the user is navigated to the dialog corresponding to the department.
-->
<form id="choose_dept">
 <field name="dept">

  <grammar src="depts-voice.grxml" mode="voice" type="application/srgs+xml"/>

  <!--Choose a department, or quit, checkout, etc. -->
  <prompt>
   <audio src="wiz_transition_2sec.wav"/>
   <audio src="wiz_selectdept.wav">
     Please choose a department</audio>
   <break time="200"/>
   <foreach item="oDept" array="aDepts">   
     <audio expr="oDept.GetID()"><value expr="oDept.GetName()"/></audio>
   </foreach>
   <break time="600"/>
   <audio src="wiz_selectwelcome.wav">
     For the main menu, say 'welcome'</audio>
  </prompt>

  <nomatch>
   <audio src="wiz_nomatch.wav">Sorry. I did not understand you.</audio>
   <reprompt/>
  </nomatch>

  <noinput>
   <prompt>Sorry. I didn't hear you.</prompt>
   <reprompt/>
  </noinput>

  <filled>
     <!-- track current department -->
     <assign name="oCurDept" expr="oStore.GetDeptById(dept)"/> 
     <if cond="oCurDept != null">
       <goto next="#choose_item"/>
     <else/>
       <prompt>Sorry. That department is currently unavailable.</prompt>
       <assign name="dept" expr="undefined"/>
       <reprompt/>
     </if>
  </filled>

 </field>
</form>

<form id="choose_item">
  <var name="aItems" expr="oCurDept.GetItems()"/>
 <block>  
  <audio src="wiz_deptwelcome.wav">Welcome to the</audio>
  <prompt><value expr="oCurDept.GetName()"/></prompt>
  <audio src="wiz_deptdept.wav">department.</audio>
  <audio src="wiz_deptitems1.wav">There are</audio>
  <prompt><value expr="aItems.length"/></prompt>
  <audio src="wiz_deptitems2.wav">items for sale today.</audio>
 </block>

 <field name="item">
  <grammar src="items-voice.grxml" mode="voice" type="application/srgs+xml"/>

  <!--For each item in the list, play audio (in this case, TTS -->
  <prompt>
   <foreach item="oItem" array="aItems">
      <audio expr="oItem.GetID()"><value expr="oItem.GetName()"/></audio>
   </foreach>
   <break time="600"/>
   <audio src="wiz_deptadd.wav">
     To add an item to your cart, say its name.</audio>
   <break time="200"/>
   <audio src="wiz_deptchange.wav">
     To go to another department, say change department.</audio>
   <break time="200"/>
   <audio src="wiz_hearcheck.wav">
     To purchase the items in your cart, say check out.</audio>
  </prompt>

  <nomatch>
   <audio src="wiz_nomatch.wav">
     Sorry. I did not understand you.</audio>
   <reprompt/>
  </nomatch>

  <noinput>
   <audio src="wiz_deptrepeat.wav">
     Let's repeat the items for sale in this department</audio>
   <reprompt/>
  </noinput>

  <filled>
   <assign name="oCurItem" expr="oStore.GetItemById(item)"/>
   <if cond="oCurItem != null">
     <goto next="#add_to_cart"/>
   <else/>
     <prompt>Sorry. That item is currently unavailable.</prompt>
     <assign name="item" expr="undefined"/>
     <reprompt/>  
   </if>
  </filled>
 </field>
</form>

<!-- this dialog adds an item to the virtual shopping cart. -->
<form id="add_to_cart">
 <block>   
   <audio src="wiz_cartadd1.wav">Adding</audio>
   <prompt><value expr="oCurItem.GetName()"/></prompt>
   <audio src="wiz_cartadd2.wav">to your cart.</audio>
   <script>oCart.Add(oCurItem.GetID())</script>
   <audio src="wiz_cartreturn1.wav">Returning you to the</audio>
   <prompt><value expr="oCurDept.GetName()"/></prompt>
   <audio src="wiz_deptdept.wav">department.</audio>
   <goto next="#choose_item"/>
 </block>
</form>

<!-- this dialog lists the items in the user's virtual shopping cart -->
<form id="list_cart">
 <var name="item_count" expr="oCart.GetDistinctItemCount()"/>
 <block>
 	<if cond="item_count &gt; 0">
       <var name="aItems" expr="oCart.GetItems()"/>
       <audio src="wiz_cartlist1.wav">The following</audio>
       <prompt><value expr="item_count"/></prompt>
       <audio src="wiz_cartlist2.wav">items are in your shopping cart:</audio>
       <foreach item="oItem" array="aItems">
          <prompt><value expr="oStore.GetItemById(oItem.key).GetName()"/></prompt>
       </foreach>
       <audio src="wiz_cartmain.wav">Returning to main menu</audio>
   <else/>
      <prompt>Your shopping cart is empty.</prompt>
   </if>
   <goto next="#welcome"/>
 </block>
</form>

<!-- 
This dialog represents the virtual checkout counter.
playback the contents of the user's cart, 
calculate the total price, 
confirm the purchase
-->
<form id="checkout">
  <var name="item_count" expr="oCart.GetDistinctItemCount()"/>
  <block>
    <if cond="item_count &gt; 0">
       <assign name="total_price" expr="oStore.CalculateTotalPrice(oCart)"/>
       <var name="aItems" expr="oCart.GetItems()"/>
       <audio src="wiz_transition_2sec.wav"/>
       <audio src="wiz_checkwelcome.wav">Welcome to the check out counter.</audio>
       <audio src="wiz_cartlist1.wav">The following</audio>
       <prompt><value expr="item_count"/></prompt>
       <audio src="wiz_cartlist2.wav">items are in your shopping cart:</audio>
       <foreach item="oItem" array="aItems">
          <prompt><value expr="oStore.GetItemById(oItem.key).GetName()"/></prompt>
       </foreach>
       <break time="600"/>
    <else/>
      <prompt>Your shopping cart is empty. Thanks for browsing.</prompt>
      <exit/>
    </if>
  </block>

 <field name="cmd">
  
  <grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0"
         xml:lang="en-US">
    <rule id="root_rule" scope="public">
      <one-of>
        <item>
          <one-of>
            <item>
              buy
              <item repeat="0-1">
                now
              </item>
            </item>
            <item>
              yes
            </item>
          </one-of>
          <tag>out.cmd = "buy_now";</tag>
        </item>
        <item>
          <one-of>
            <item>
              cancel
            </item>
          </one-of>
          <tag>out.cmd = "cancel";</tag>
        </item>
        <item>
          <one-of>
            <item>
              repeat
            </item>
            <item>
              <item repeat="0-1">
                say
              </item>
              again
            </item>
          </one-of>
          <tag>out.cmd = "repeat";</tag>
        </item>
      </one-of>
    </rule>

  </grammar>

  
  <prompt>
   <audio src="wiz_checkprice1.wav">The total price is</audio>
   <prompt>
     <say-as type="currency"><value expr="total_price"/></say-as>
   </prompt>
   <break time="600"/>
     <audio src="wiz_checkbuy.wav">
        Please say 'buy' 'now' to pretend to purchase these items, 
        and to bill them to your imaginary credit card.</audio>
   <break time="500"/>
   <audio src="wiz_checktocancel.wav">
     Or, just say 'cancel' to empty your cart 
      and return to the main menu.</audio>
   <break time="300"/>
   <audio src="wiz_checktorepeat.wav">
     To repeat the items in your shopping cart, say repeat.</audio>
  </prompt>
  
  <nomatch>
   <audio src="wiz_nomatch.wav">Sorry. I did not understand you.</audio>
   <reprompt/>
  </nomatch>

  <noinput>
   Sorry. I didn't hear you.
   <reprompt/>
  </noinput>

  <filled>
   <if cond="cmd=='buy_now'">
     <goto next="#purchase"/>
   <elseif cond="cmd=='cancel'"/>
     <audio src="wiz_checkcancel.wav">
       Cancelling your order and returning you to the main menu.</audio>
     <goto next="#main"/>
   <elseif cond="cmd=='repeat'"/>
     <clear/>
   </if>
  </filled>

 </field>
</form>

<!-- this is the final dialog. It confirms the user's virtual purchase. -->
<form id="purchase">
 <block>
   <audio src="wiz_checkpurch1.wav">Purchasing the</audio>
   <prompt><value expr="oCart.GetDistinctItemCount()"/></prompt>
   <audio src="wiz_checkpurch2.wav">items in your cart for</audio>
   <prompt><value expr="total_price"/></prompt>
   <audio src="wiz_checkpatron.wav">Thank you for your valued patronage.</audio>
   <audio src="wiz_checkgoodbye.wav">Goodbye!</audio>
   <exit/> 
 </block>
</form>

<!-- In this dialog the user is asked to confirm 
  that they want to empty their cart and start over -->
<form id="confirm_restart">
   <var name="count" expr="oCart.GetDistinctItemCount()"/>
   <field name="yesno" type="boolean">
      <prompt>      
      <if cond="count &gt; 0">
      You have <value expr="count"/> items in your shopping cart.
      </if>
      Are you sure you wanna start over?
      </prompt>
      
      <catch event="noinput nomatch">
        <audio expr="sUrlEarcons + 'nomatch.wav'"></audio>
        I'm sorry. I didn't get that. Say yes to start over. Say no to continue.
      </catch>

      <filled>
         <if cond="yesno">
            <goto next="#main"/>
         <else/>
            <goto next="#welcome"/>
         </if>
      </filled>
   </field>
</form>


</vxml>

In this example, manipulation of the DOM is encapsulated within the user-defined JavaScript classes defined in "grocerystore.js". The code follows:

// encapsulate the store in a js object
// constructor
function CStore() {}

// initialize the store with a DOM fetched via the data tag
CStore.prototype.Init = function(dom) {
  var bRet = true;
  this._dom = null;

  if (typeof (dom) == 'object') {
    this._dom = dom;
  }
  else {
    bRet = false;
  }
  return bRet;
}

CStore.prototype.GetName = function() {
  return GetChildText(this._dom.documentElement, "name", "unknown");  
}

// return a js array of department objects
CStore.prototype.GetDepartments = function() {
  var aDepts = [];
  var oRoot = this._dom.documentElement;
  for (var i = 0; i < oRoot.childNodes.length; i++) {
    var oChild = oRoot.childNodes.item(i);
    if (oChild.nodeName == "dept") {
      aDepts.push(new CDept(oChild));
    }
  }
  return aDepts;
}

// return a user-defined department object 
// given the departments id (typically returned by the grammar)
CStore.prototype.GetDeptById = function(id) {
  var oNode = this._dom.getElementById(id);
  return (oNode != null ? new CDept(oNode) : null);
}

// return a user-defined item object 
// given the item's id (typically returned by the grammar)
CStore.prototype.GetItemById = function(id) {
  var oNode = this._dom.getElementById(id);
  return (oNode != null ? new CItem(oNode) : null);
}

// Given a shopping cart, calculate the grand total for the items
CStore.prototype.CalculateTotalPrice = function(oCart) {
  var nTotal = 0;
  var aCartItems = oCart.GetItems();
  for (var i = 0; i < aCartItems.length; i++) {
    var oItem = this.GetItemById(aCartItems[i].key);
    if (oItem != null) {
      nTotal += (oItem.GetPrice() * aCartItems[i].count);
    }
  }
  return nTotal;
}

// encapsulate a department in a js object
function CDept(oDept) {
  this._node = oDept;
}

// return the department name
CDept.prototype.GetName = function() {
  return GetChildText(this._node, "name", "unknown");
}

// return the department id
CDept.prototype.GetID = function() {
  return this._node.getAttribute("id");
}

// return a js array of hashes that store 
// the name and price of each item in the department
CDept.prototype.GetItems = function() {
  var aItems = [];
  for (var i = 0; i < this._node.childNodes.length; i++) {
    var oNode = this._node.childNodes.item(i);
    if (oNode.nodeName == "item") {    
      aItems.push(new CItem(oNode));
    }
  }
  return aItems;
}

// encapsulate a store item
function CItem(oItem) {
  if (typeof(oItem) == 'object') { 
    this._name = GetChildText(oItem, "name");
    this._price = GetChildText(oItem, "price");
    this._id = oItem.getAttribute("id");
  }  
}

CItem.prototype.GetName = function() {
  return this._name;
}

CItem.prototype.GetID = function() {
  return this._id;
}

CItem.prototype.GetPrice = function() {
  return this._price;
}

// get the text of the first child with the given name
function GetChildText(oParent, sName, sDefault) {
  var ret = sDefault;
  for (var i = 0; i < oParent.childNodes.length; i++) {
    var oChild = oParent.childNodes.item(i);
    if (oChild.nodeName == sName) {
      ret = oChild.firstChild.data;
      break;
    }
  }  
  return ret;
}

function Log(s) {
  WScript.Echo(s)
}

The user-defined classes include:

CStore Encapsulates the grocery store data.
CDept Encapsulates the data related to a department in the grocery store.
CItem Encapsulates the data related to an item in any department in the grocery store.

After fetching the store data in the main form, the application passes a reference to the DOM (domStore) to the Init method of an instance of the CStore class. All access to store data begins by making method calls through an instance of CStore and the CDept and CItem objects that it returns. For example, to present a list of department names to the caller in the choose_dept form, the application uses a foreach element to queue the department names. The array of CDept objects is retrieved by calling the GetDepartments method of the CStore object.

The application allows you to add items to a virtual shopping cart. The cart is implemented as a user-defined JavaScript class, CVSCart. The implementation follows:

// a simple class encapsulating a virtual shopping cart
function CVSCart()
{
   // constant indicating every instance 
   // of a specific item should be removed. See Remove
   this.REMOVE_ALL = -1; 
   this.Init();
}

// initialize the cart
CVSCart.prototype.Init = function()
{
   // we use an associative array (hash table) to store items
   // item name is the key; item count is the value
   this._items = {};

   // number of distinct items in the cart
   this._iDistinct = 0;
}

// add iCount items
CVSCart.prototype.Add = function(sKey, iCount)
{
   if (isNaN(iCount))
   {
      iCount = 1;
   }
   
   if (null == this._items[sKey])
   {
      // adding a distinct new item
      this._items[sKey] = iCount;
      this._iDistinct += 1;
   }
   else
   {
      // adding to the count of an existing item
      this._items[sKey] += iCount;
   }
}

// nix all instances of the specified item
CVSCart.prototype.RemoveAll = function(sNixItem)
{
   var bSuccess = true;
    if (this._items[sNixItem] != null)
    {
      delete(this._items[sNixItem]);
      this._iDistinct -= 1;
   }
   else
   {
      bSuccess = false;
   }

   return bSuccess;
}

// remove iCount sNixItem's 
CVSCart.prototype.Remove = function(sNixItem, iRemove)
{
   var bSuccess = true;

   if (isNaN(iRemove))
   {
      iRemove = 1;
   }

   if (this._items[sNixItem] != null)
   {
      if (iRemove == this.REMOVE_ALL)
      {
         this.RemoveAll(sNixItem);
      }
      else
      {
         var iCurCount = this._items[sNixItem] -= iRemove;
         if (iCurCount <= 0)
         {
            this.RemoveAll(sNixItem);
         }
      }      
   }
   else
   {
      bSuccess = false;
   }

   return bSuccess;
}

// return the number of distinct items in the cart
CVSCart.prototype.GetDistinctItemCount = function()
{
   return this._iDistinct;
}

// return the number of items of a particular type in the cart
CVSCart.prototype.GetCountOf = function(sKey)
{
   var iCount = this._items[sKey];
   return (null == iCount ? 0 : iCount);
}

// return an array of tuples (name, count) of the items in the cart
CVSCart.prototype.GetItems = function()
{
   var aItems = [];

   for (var sKey in this._items)
   {
      aItems.push({'key' : sKey, 'count' : this._items[sKey]});
   }
   
   return aItems;
}


The return value from the department and item grammars used in the choose_dept and choose_item dialogs correspond directly to the unique identifier of the correspondong department and item in the XML data returned by the application server.

Here's the department grammar:

<?xml version= "1.0"?>


<grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         version="1.0"
         xml:lang="en-US"
         xmlns="http://www.w3.org/2001/06/grammar">
  <rule id="root_rule" scope="public">
    <one-of>
      <item>
        meats
        <tag>out.dept = "d1";</tag>
      </item>
      <item>
        vegetables
        <tag>out.dept = "d2";</tag>
      </item>
      <item>
        fruits
        <tag>out.dept = "d3";</tag>
      </item>
      <item>
        desserts
        <tag>out.dept = "d4";</tag>
      </item>
    </one-of>
  </rule>

</grammar>

When, for example, the user says "desserts", the voice recognizer returns "d4". Using the DOM, it's easy to retrieve the corresponding "dept" node in the XML data. See the GetDeptById method of the CStore class.

Here's the item grammar:

<?xml version= "1.0"?>


<grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         version="1.0"
         xml:lang="en-US"
         xmlns="http://www.w3.org/2001/06/grammar">
  <rule id="root_rule" scope="public">
    <one-of>
      <item>
        crocodile
        liver
        <tag>out.item = "i10";</tag>
      </item>
      <item>
        chicken
        wings
        <tag>out.item = "i11";</tag>
      </item>
      <item>
        gazelle
        sausage
        <tag>out.item = "i12";</tag>
      </item>
      <item>
        duck
        breast
        <tag>out.item = "i13";</tag>
      </item>
      <item>
        eggplant
        <tag>out.item = "i20";</tag>
      </item>
      <item>
        artichokes
        <tag>out.item = "i21";</tag>
      </item>
      <item>
        tomatoes
        <tag>out.item = "i22";</tag>
      </item>
      <item>
        sweet
        potatoes
        <tag>out.item = "i23";</tag>
      </item>
      <item>
        fuji
        apples
        <tag>out.item = "i30";</tag>
      </item>
      <item>
        yummy
        yummy
        mangoes
        <tag>out.item = "i31";</tag>
      </item>
      <item>
        strawberries
        <tag>out.item = "i32";</tag>
      </item>
      <item>
        nectarines
        <tag>out.item = "i33";</tag>
      </item>
      <item>
        ben
        and
        jerrys
        <tag>out.item = "i40";</tag>
      </item>
      <item>
        figgy
        pudding
        <tag>out.item = "i41";</tag>
      </item>
      <item>
        chocolate
        cookies
        <tag>out.item = "i42";</tag>
      </item>
      <item>
        myer
        lemons
        <tag>out.item = "i43";</tag>
      </item>
    </one-of>
  </rule>

</grammar>

Given a snapshot of the XML data returned by the data server, it's possible to generate both the department and item grammar. Here is the XSL stylesheet used to generate the department grammar:

<!-- generate a department grammar from the store data -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>

<xsl:template match="/">
[
  <xsl:apply-templates select="market/dept"/>
]
</xsl:template>

<xsl:template match="dept">
  (<xsl:value-of select="name"/>) 
  <xsl:text disable-output-escaping="yes">
    {&lt;</xsl:text>dept 
      "<xsl:value-of select="@id"/>"<xsl:text disable-output-escaping="yes">&gt;}
  </xsl:text>
</xsl:template>

</xsl:stylesheet>


Here is an XSL stylesheet used to generate the item grammar:

<!-- generate an item grammar from the store data -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>

<xsl:template match="/">
[
  <xsl:apply-templates select="market/dept/item"/>
]
</xsl:template>

<xsl:template match="item">
  (<xsl:value-of select="name"/>) 
  <xsl:text disable-output-escaping="yes">
   {&lt;</xsl:text>item 
     "<xsl:value-of select="@id"/>"<xsl:text disable-output-escaping="yes">&gt;}
  </xsl:text>
</xsl:template>

</xsl:stylesheet>


Observe that this stylesheet generates a single grammar that includes all items in all departments. That means that, for example, if you are shopping in the "vegetable" department, the application will still recognize "chicken wings". While this works for the small set of items represented in this sample application, a real application with thousands of items in a single department would likely need to generate distinct grammars for each department. The following stylesheet generates a grammar that only includes the items in the department specified by the "dept" parameter. Please consult the documentation of your XSLT processor to learn how to pass parameters to your stylesheet.

<!-- generate an item grammar for a particular department from the store data -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>

<xsl:param name="dept" select="'d1'"/>

<xsl:template match="/">
[
  <xsl:apply-templates select="market/dept[@id=$dept]/item"/>
]
</xsl:template>

<xsl:template match="item">
  (<xsl:value-of select="name"/>) 
  <xsl:text disable-output-escaping="yes">
   {&lt;</xsl:text>item 
     "<xsl:value-of select="@id"/>"<xsl:text disable-output-escaping="yes">&gt;}
  </xsl:text>
</xsl:template>

</xsl:stylesheet>


[24]7 Inc.| Terms of Service| Privacy Policy| General Disclaimers