构建健壮的语音应用程序意味着在有经验的用户和小白用户的需求之间取得平衡。有经验的用户通常希望跳转到他们已经知道的选项;小白用户通常希望他们能得到相应的帮助。与大多数现代编程语言一样,Voice XML提供了一个结构化的应用程序框架,它支持事件、处理程序和范围规则,使开发人员能够微调语音应用程序在不同上下文中的响应方式。
了解VXML基础知识(如dtmf、field等)可以帮助快速构建有用且功能强大的语音应用程序。然而,有时需要超越这些基础知识,交付更复杂、更鲁棒性和响应更快的语音应用程序。例如,用户可能希望提供常规的以及特定于上下文的帮助消息,或者允许有经验的用户中断对话框并跳转到特定的选项, 而不被提示。为了编写允许这种跨对话框跳转的应用程序,Voice XML支持类似于编程语言中的作用域规则。在Voice XML中,语法可以定位在字段、表单或文档级别。此外,我们还可以通过将多个文档组合为一个应用程序来响应整个文档集合中的输入。具体的编辑要点需要注意下面几个方向:
Barging in with bargein
默认情况下,VXML应用程序允许用户直接打断对话提示,进行输入。这种中断能力是由VXML元素中的barge in属性控制的,这有利于经常使用的用户,他们可以根据需要快速获取所需的信息,从而绕过冗长的提示音。但是,开发人员可以选择性地禁用bargein,以确保用户监听完整的信息性消息或广告。此外,可以禁用bargein以防止虚假的后台对话触发活动语法。
下图演示了如何禁用bargein(第4行)并强制用户收听广告。由于例1中的表单关闭了bargein,其作用范围仅限于当前字段,所以文档中可能出现的其他对话框不会关闭bargein。如果在文档级别关闭bargein将默认关闭文档中所有对话的bargein。
1 <vxml version="2.1">
2 <form id="main">
3 <!--inside the form we disable bargein for this dialog -->
4 <property name="bargein" value="false"/>
5 <!-- play an ad -->
6 <block>
7 <audio src="http://www.zorko.com/ads/paynow.wav"/>
8 </block>
9 <!-- dialog -->
10 <field name="city">
11 <prompt>What city are you calling from?</prompt>
12 <grammar src="city.grammar"/>
13 </field>
14 </form>
15 </vxml>
当bargein为真时,用户可以直接跳出一个对话框,并快速跳转到其他对话框。为了实现这个功能,需要激活多个语法,以便在有语音或dtmf输入匹配时跳转对话字段。
先看一个简单的例子,说明如何在单个文档中完成这项工作,然后再看多文档操作。
图2演示了包含多个对话框的文档的语法范围。示例代码使用VXML link元素,该元素使我们能够定义一个目标对话框和一个语法,该语法定义将跳转的对话框Id。链接元素的结构如下:
<link next="#mydialog">
<grammar mode="voice">
<!--grammar goes here -->
</grammar>
</link>
在例2中,link元素显示为vxml元素的一个子元素,作用范为整个文档。这意味着在文档中包含的任何菜单或表单中,与其中一个链接语法的匹配将跳转至目标对话框。
此示例还包含两个链接元素:一个将使我们快速转换到baseball菜单,另一个将带我们转到id=“mets”的表单。
在第13行中,link元素包含一个用于单个单词棒球的语法。请注意,我们的示例包含两个链接元素:一个用于转换到“baseball”对话框(第10行),另一个用于转换到“mets”对话框(第18行)。
还要注意,第18行的链接包含两个语法:一个是语音,另一个是dtmf。因此,如果用户在任何时候说“mets”或“New York mets”或按9,均会跳转到id为“mets”的表单。
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE vxml SYSTEM "http://www.w3.org/TR/voicexml20/vxml.dtd">
3
4 <vxml version = "2.0" xmlns=’http://www.w3.org/2001/vxml’
5 xmlns:xsi=’http://www.w3.org/2001/XMLSchema-instance’
6 xsi:schemaLocation=’http://www.w3.org/2001/vxml
7 http://www.w3.org/TR/voicexml20/vxml.xsd’>
8
9
10 <link next="#baseball">
11 <grammar mode="voice">
12 <rule id="linkbaseball" scope="public">
13 baseball
14 </rule>
15 </grammar>
16 </link>
17
18 <link next="#mets">
19 <grammar>
20 <rule id="linkmets" scope="public">
21 <one-of>
22 <item>mets</item>
23 <item>new york mets</item>
24 </one-of>
25 </rule>
26 </grammar>
27
28 <grammar mode="dtmf" >
29 <rule id="linkmets2" scope="public">9</rule>
30 </grammar>
31
32 </link>
33
34
35 <menu id="mainmenu" dtmf="true">
36 <prompt>
37 Welcome to the info hotline. If you know the category you want,
38 you may say it at any time.
39 <enumerate>
40 For <value expr="_prompt"/>, press <value expr="_dtmf"/>
41 </enumerate>
42 </prompt>
43
44
45 <choice next="#sports">sports</choice>
46 <choice next="#weather">weather</choice>
47 </menu>
48
49 <menu id="sports">
50 <property name="inputmodes" value="dtmf"/>
51 <prompt>
52 For baseball press 1, For football press 2, For soccer
53 press 3.
54 </prompt>
55 <choice dtmf="1" next="#baseball"/>
56 <choice dtmf="2" next="#football"/>
57 <choice dtmf="3" next="#soccer"/>
58 </menu>
59
60
61 <form id="weather">9
62 <block> You have reached the weather line. Whether it’s cold,
63 or whether it’s hot, we’re going to have weather,
64 whether or not.
65 </block>
66 </form>
67
68 <menu id="baseball">
69 <property name="inputmodes" value="dtmf"/>
70 <prompt>
71 For yankees press 1, For mets press 2, For giants press 3.
72 </prompt>
73 <choice dtmf="1" next="#yankees"/>
74 <choice dtmf="2" next="#mets"/>
75 <choice dtmf="3" next="#giants"/>
76 </menu>
77
78 <form id="soccer">
79 <block> you have reached the soccer hotline.
80 </block>
81 </form>
82
83 <form id="football">
84 <block> you have reached the football hotline.
85 </block>
86 </form>
87
88 <form id="mets">
89 <block> The mets are contenders for the world series.
90 </block>
91 </form>
92
93 <form id="yankees">
94 <block> George Stein Brenner rules his roost.
95 </block>
96 </form>
97
98 <form id="giants">
99 <block> Barry Bonds has now hot more home runs than Hank Aaron.
100 </block>
101 </form>
102
103 </vxml>
在例2中,所有的menu和form都包含在同一个文档中,因此我们对link元素的定位(使用document scope)确保链接语法在所有对话框中都是可操作的。但是,如果我们的菜单和表单设置为单独的文档,则在转换到另一个文档后,快捷文档链接语法将不再可用。为了解决这个问题,以便语法、变量和属性在一系列单独的文档中保持可见,Voice XML支持应用程序的概念。
Multiple Document Applications
Voice XML多文档应用程序是围绕单个根文档和一个或多个叶子文档构建的。根文档中定义的任何语法或变量对叶子文档都是可见的。若要设置为使多个文档作为一个应用程序协同工作,首先应选择一个文档作为应用程序根文档;其他文档被视为叶子文档。要被视为根文档,只需要另一个文档在<vxml>元素中将其命名为应用程序文档。
当应用程序配置了根和叶子文档时,每次加载叶子文档时,都会检查根文档是否也已加载。否则,服务器首先加载根目录。在服务器加载属于不同应用程序的文档之前,该应用程序的根文档将一直保留在作用域中。加载叶子文档时,会导致根文档也被加载,但不会执行根文档中的任何对话框,执行将从叶子文档中开始。
例3演示了一个包含根文档和两个叶子文档的多文档应用程序。
根文档,myroot.vxml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <vxml xmlns="http://www.w3.org/2001/vxml"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.w3.org/2001/vxml
5 http://www.w3.org/TR/voicexml20/vxml.xsd"
6 version="2.0">
7 <var name="app" expr="’world’"/>
8
9 <link next="baseball.vxml">
10 <grammar type="application/srgs+xml" root="root" version="1.0">
11 <rule id="root" scope="public">baseball</rule>
12 </grammar>
13 </link>
14 </vxml>
叶子文档,mainmenu.vxml
16 <?xml version="1.0" encoding="UTF-8"?>
17 <vxml xmlns="http://www.w3.org/2001/vxml"
18 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19 xsi:schemaLocation="http://www.w3.org/2001/vxml
20 http://www.w3.org/TR/voicexml20/vxml.xsd"
21 version="2.0" application="myroot.vxml">
22
23 <menu id="mainmenu" dtmf="true">
24 <prompt>
25 Welcome to the info <value expr="app"/> hotline.
26 If you know the category you want,
27 you may say it at any time.
28 <enumerate>
29 For <value expr="_prompt"/>, press <value expr="_dtmf"/>
30 </enumerate>
31 </prompt>
32
33 <choice next="#sports">sports</choice>
34 <choice next="#weather">weather</choice>
35 </menu>
36
37 <menu id="sports">
38 <property name="inputmodes" value="dtmf"/>
39 <prompt>
40 For baseball press 1, For football press 2, For soccer
41 press 3.
42 </prompt>
43 <choice dtmf="1" next="baseball.vxml"/>
44 <choice dtmf="2" next="#football"/>
45 <choice dtmf="3" next="#soccer"/>
46 </menu>
47
48
49 <form id="weather">
50 <block> you have reached the weather it rains or not hotline
51 </block>
52 </form>
53
54 <form id="football">
55 <block> you have reached the football, kick kick hotline
56 </block>
57 </form>
58
59 <form id="soccer">
60 <block> you have reached the soccer, push push hotline
61 </block>
62 </form>
63
64
65 </vxml>
叶子文档,baseball.vxml
66 <?xml version="1.0" encoding="UTF-8"?>
67 <vxml xmlns="http://www.w3.org/2001/vxml"
68 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
69 xsi:schemaLocation="http://www.w3.org/2001/vxml
70 http://www.w3.org/TR/voicexml20/vxml.xsd"
71 version="2.0" application="myroot.vxml">
72
73
74 <form>
75 <block>welcome to baseball news. brought to you by
76 the info <value expr="app"/> hotline.
77 </block>
78 </form>
79
80 </vxml>
在例3中,mainmenu.vxml的初始加载(第16行)强制加载myroot.vxml(第1行)。这将创建应用程序变量app(第7行)并定义一个链接(第9行),每当用户说“basketball”时,该链接将强制转换到basketball.vxml。当应用程序启动时,用户会听到主菜单对话框。
在这个示例对话框中,用户跳起来听关于basketball的内容。
S: Welcome to the info world hotline. If you know the category you want, you may say it at any time. For sports, press 1. For weather, press 2.
U: Baseball.
S: Welcome to baseball news, brought to you by the info world hotline.
注意,当转换到basketball.vxml时,根文档(第7行)中定义的变量app可用(第76行)。还要注意<vxml>元素(第2-6行)没有指定应用程序属性。在<vxml>元素的application属性(第21行和第71行)中,由一个叶子文档来引用它的应用程序文档。
如果加载的文档未指定应用程序属性,则此文档将成为新应用程序的根文档。
Multidocument Transitions
在多文档应用程序中,一次最多加载两个文档:应用程序根文档和应用程序叶子文档。当使用<choice>、<goto><link>或<submit>元素从一个文档转换到另一个文档时,一些转换将在应用程序中进行,而另一些则可能设置新的应用程序上下文。
现有应用程序的保留或新根上下文的初始化将取决于转换的类型。可能性包括:
应用程序中的根到叶:当前文档是根文档(没有应用程序属性)并且目标文档包含引用当前文档的应用程序属性时,在同一应用程序中发生根到叶转换。发生这种情况时,应用程序根上下文将被保留。
应用程序中的叶到叶:当前文档是叶子文档并且目标文档的应用程序属性值解析为根文档的URI时,在同一应用程序中发生叶到叶转换。将保留应用程序根上下文。
应用程序中的叶到根:当前文档是叶子文档而目标文档的URI是根文档时,同一应用程序中的叶到根转换发生。
根到根:当前文档是根文档而目标文档是根文档时,会发生根到根的转换;也就是说,不再保留应用程序属性。
转换到另一个应用程序的叶:当加载的文档包含不是当前根文档的应用程序属性时,将发生到新应用程序的转换。这将导致加载新的根文档和新的叶文档。
Throwing and Catching Events
基本的事件处理。当用户没有响应、请求帮助或没有以应用程序理解的方式响应时,解释器会以特定于平台的默认值响应,例如:输入不匹配时,反馈“我不理解您所说的话”。
前面的示例演示了开发人员如何通过在字段、窗体、文档或应用程序级别提供<noinput>、<help>或<nomatch>元素来重写此默认行为。例如,考虑没有响应的情况。如果我们提供一个<noinput>元素,如下所示,用户将收到一个提示:“您一直没有输入,请输入账号”:
<catch event=“noinput”>您一直没有输入,请输入账号</noinput>
不难发现Voice XML是基于事件的,很像java Swing这样的编程框架。默认情况下,符合Voice XML的平台为noinput、help、nomatch、cancel、exit和error事件提供隐式catch处理程序(即直接写noinput)。如果处理程序是由开发人员提供的,则它们会覆盖默认值。下表总结了预定义系统事件的catch处理程序的默认系统行为。
下例是对多文档catch元素操作,具体如下:
<vxml version="2.0" xmlns="http://www.w3.org/2001/vxml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2001/vxml
http://www.w3.org/TR/voicexml20/vxml.xsd">
<catch event="nomatch noinput" count="5">
<prompt>Security violation! The f b i is being notified.</prompt>
</catch>
<form id="getData">
<catch event="nomatch noinput" count="4">
<prompt>You are now in big, big, trouble. </prompt>
</catch>
<field name="user_id" type="digits">
<prompt>What is your numeric user I D</prompt>
</field>
<field name="password">
<prompt>What is the code word?</prompt>
<grammar version="1.0" root="root">
<rule id="root" scope="public">bingo</rule>
</grammar>
<help>It is the name of a popular multi-player game.</help>
<catch event="nomatch noinput" count="3">
<prompt>Security violation! You are in big trouble.</prompt>
</catch>
</field>
</form>
</vxml>
下面的示例对话框演示了多个catch处理程序的触发:
S: What is your numeric user I D
U: 1224
S: What is the code word?
U: sassafrass
S: I did not understand what you said <default platform response>
S: What is the code word?
U: monkey
S: I did not understand what you said <default platform response>
S: What is the code word?
U: zorro
S: Security violation! You are in big trouble. <catch with count = 3>
S: What is the code word?
U: mellow yellow
S: You are now in big, big, trouble. <catch with count = 4>
S: What is the code word?
U: haricot vert
S: Security violation! The f b i is being notified. <catch with count = 5>
在前面的对话中,当抛出nomatch事件时,将检查处理该事件的作用域及其作用域,以找到最符合条件的catch元素。
当前作用域和所有封闭作用域(表单项、表单、文档、应用程序根文档、解释器上下文)中的所有捕获首先按作用域排序(从当前作用域开始),然后按文档顺序在每个作用域中排序。对于具有与抛出的事件匹配的事件名称的捕获,将选择具有匹配计数的捕获。
在处理多个事件时,可以使用catch元素的特殊变量event编写一个catch处理程序,该变量包含抛出的事件的名称。例如,以下catch元素处理两种类型的事件:
<catch event="event.zorkon.foo event.zorkon.bar">
<if cond="_event==’event.zorkon.foo’">
<!-- Play event.zorkon.foo audio -->
<audio src="foo.wav"/>
<else/>
<!-- Play event.zorkon.bar audio -->
<audio src="bar.wav"/>
</if>
<!-- Continue with common handling for either event -->
</catch>
检查事件变量会根据抛出的事件选择要播放的音频。这里将为event.zorkon.foo事件播放foo.wav文件。将为event.zorkon.bar事件播放bar.wav文件。catch元素的其余部分也可能包含两种事件类型通用的可执行内容。
此外,catch元素的匿名变量作用域还包括特殊变量_message,它包含来自相应<throw>元素的消息字符串的值。例如,如果我们用以下方式抛出事件:
<throw event="com.zorkon.event.foo" message="strange input received"/>
会收到如下
<catch event="com.zorkon.event.foo">
. . .
<value expr="_message">
. . .
</catch>
Application-Specific Events
现在让我们看看应用程序如何抛出自己的特定的事件来定制响应。在下面的例子中,当boss打电话给他的手机(214.322.6666)时,我们想采取特殊的措施,并且不能调用代码来访问他的数据
1 <vxml version="2.0" xmlns="http://www.w3.org/2001/vxml"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://www.w3.org/2001/vxml
4 http://www.w3.org/TR/voicexml20/vxml.xsd">
5
6
7 <catch event="com.zorkon.bossHasProblem">
8 <prompt>Sir, we are so sorry you are having trouble.
9 Some one will contact you shortly.</prompt>
10 <submit next="com.zorkon/bossproblem.jsp"/>
11 <exit/>
12 </catch>
13
14
15 <form id="getData">
16
17 <field name="user_id" type="digits">
18 <prompt>What is your numeric user I D</prompt>
19 </field>
20
21 <field name="password">
22 <prompt>What is the code word?</prompt>
23 <grammar version="1.0" root="root">
24 <rule id="root" scope="public">bingo</rule>
25 </grammar>
26
27 <nomatch>
28 <if cond="session.callerid == ’2143226666’">
29 <throw event="com.zorkon.bossProblem"/>
30 <else/>
31 <throw event="com.zorkon.userProblem"/>
32 </if>
33 </nomatch>
34
35 <help>It is the name of a popular multi-player game.</help>
36
37 <catch event="noinput com.zorkon.userProblem">
38 <prompt>Security violation! You are in big trouble.</prompt>
39 </catch>
40
41 </field>
42
43 </form>
44 </vxml>
在上例中,我们通过内置的变量session.callerid使用对boss电话的先验知识来决定抛出哪个事件。我们处理nomatch事件(第27行)以捕获语法输入错误,然后抛出两个特定于应用程序的事件之一,com.zorkon.bossProblem(第29行)或com.zorkon.userProblem(第31行),具体取决于呼叫是从老板的电话还是从其他地方传入。注意,我们不需要做任何特殊的事情来创建自己的应用程序事件。所需的只是为我们要引发的事件命名,如下所示:
<throw event="com.zorkon.bossProblem"/>
Event Names and Prefix Matching
事件命名和匹配规则
在事件名称(例如com.zorkon.bossProblem)中使用"."不仅使事件可读,名称中的点还相当于定义了一个层次结构,该层次结构可用于为各种事件提供处理程序。例如,当catch元素事件 属性指定要抛出的事件的全名的标记前缀时,会发生前缀匹配。例如,以下将匹配zorkon.disconnect.transfer, zorkon.disconnect.drop或任何以zorkon.disconnect开头的事件 :
<catch event="zorkon.disconnect">
<prompt>Caught a connection dot disconnect event</prompt>
</catch>
最简单的方法就是简单地指定“.”,如下所示,它匹配所有事件(没有事件属性的<catch>也是如此 ):
<catch event=".">
<prompt>Caught an event</prompt>
</catch>
当使用事件前缀来捕获事件时,需要特别注意的是,catch元素优先选择文档中较早出现的catch元素,而不是较晚出现的catch元素。不太具体。因此,建议按从更具体到较不具体的顺序指定捕获元素。例如,应该按以下顺序 为“ error.foo”和“ error”指定catch元素:
<catch event="error.foo">
<prompt>Caught an error dot foo event</prompt>
</catch>
<catch event="error">
<prompt>Caught an error event</prompt>
</catch>
如果catch以相反的顺序被指定的元件,事件“error.foo”将永远不会被执行,因为 event=“error”将总是匹配优先于“error.foo”。
总结
Voice XML为围绕语法和事件构建复杂的模块化应用程序提供了一个具有范围规则的框架。基于字段、表单、文档和应用程序的层次结构,开发人员可以控制语法、变量、事件和属性的可见性。通过定义特定于应用程序的事件并将其抛出层次结构堆栈,开发人员可以定义跨多个文档和表单的相同行为。我们对Voice XML的了解越深入,就越能看到它与成熟的现代编程语言的相似之处。