第五节 文档的结构和执行
|
|
|
VoiceXML文档主要由一些叫做dialog的上层元素组成。VoiceXML有两种对话:form和menu。文档里还可以有<meta>、<metadata>、<var>、<script>、<property>、<catch>和<link>元素。 |
|
一、在一个文档中执行 |
|
文档的执行由默认的第一个对话开始。每个对话在执行的时候,都会指定下一个对话,否则执行中止。 |
|
下面举个“Hello World”的例子来说明它。这个例子里由有一个文档级的变量――hi,它的值为字符串“Hello World!”,是第一个form的提示信息。一旦第一个form播放了“Hello World”,控制会跳转到下一个叫做“say_goodbye”的form,然后播放“Goodbye!”由于第二个form没有跳转到其他的对话,文档退出,执行结束。 |
|
<?xml version="1.0" encoding="UTF-8"?>
<vxml 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"
version="2.0">
<meta name="author" content="John Doe"/>
<meta name="maintainer" content="hello-support@hi.example.com"/>
<var name="hi" expr="'Hello World!'"/>
<form>
<block>
<value expr="hi"/>
<goto next="#say_goodbye"/>
</block>
</form>
<form id="say_goodbye">
<block>
Goodbye!
</block>
</form>
</vxml>
|
|
上面的两个form也可以合起来: |
|
<?xml version="1.0" encoding="UTF-8"?>
<vxml 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"
version="2.0">
<meta name="author" content="John Doe"/>
<meta name="maintainer" content="hello-support@hi.example.com"/>
<var name="hi" expr="'Hello World!'"/>
<form>
<block>
<value expr="hi"/> Goodbye!
</block>
</form>
</vxml>
|
|
元素<vxml>有如下属性: |
version |
表示该文档的VoiceXML的版本号(必须的),当前的版本号是2.0。 |
xmlns |
表示给VoiceXML指定的域名空间(必须的)VoiceXML的域名空间是http://www.w3.org/2001/vxml。 |
xml:base |
该文档的基础URI,文档中所有的相对的URI引用都是相对于这个基础URI的。 |
xml:lang |
表示该文档的语言标识符,如果省略,默认的语言为平台特定的语言。 |
application |
表示该文档的应用根文档的URI。 |
|
表2:<vxml>元素的属性 |
|
在文档级,语言信息可以通过继承得到:那些有“xml:lang”属性的元素可以继承“xml:lang”的值,例如<grammar>和<prompt>元素,除非这些元素也指定了一个值。 |
|
二、多文档应用的执行 |
|
通常,每个文档都是作为一个孤立的应用运行的。在某些情况下,你想要多个文档作为一个应用一起运行,你可以选择一个文档作为应用根文档(application root document),其他的作为应用叶文档(application leaf document),每个叶文档在它的<vxml>元素里指定根文档。 |
|
这样的话,每次解释器要加载执行该应用的一个叶文档时,如果应用根文档还没有加载,它会先加载应用根文档。应用根文档会一直被加载,直到解释器加载另外一个应用的文档。因此,解释器在解释的时候,下面两个条件,必须满足一个: |
|
1、应用根文档已经加载,且用户正在根文档里执行,没有叶文档被加载。 |
|
2、应用根文档和一个叶文档都被加载,且用户在叶文档里执行。如果各个文档里有定义了一些subdialog,此时可能不止加载一个叶文档,但是只能在其中的一个文档中执行。 |
|
当加载一个叶文档时,虽然根文档也被加载,但是根文档中的对话不会被执行,而是在叶文档中执行。 |
|
多文档应用有几个好处: |
|
1、叶文档可以使用根文档的变量,因此一些信息能够共享,并保留下来; |
|
2、根文档的<property>元素可以给叶文档要用的一些property指定默认值; |
|
3、公有的ECMAScript代码可以放在根文档的<script>元素中,然后在叶文档中使用; |
|
4、根文档中的<catch>元素可以为叶文档指定默认的事件处理; |
|
5、根文档中作用域为document的语法在叶文档中也是激活的,这样用户就可以和根文档中的form、link和menu进行交互。 |
|
下面是一个有两个文档的应用: |
|
应用根文档(app-root.vxml): |
|
<?xml version="1.0" encoding="UTF-8"?>
<vxml 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"
version="2.0">
<var name="bye" expr="'Ciao'"/>
<link next="operator_xfer.vxml">
<grammar type="application/srgs+xml" root="root" version="1.0">
<rule id="root" scope="public">operator</rule>
</grammar>
</link>
</vxml>
|
|
叶文档(leaf.vxml): |
|
<?xml version="1.0" encoding="UTF-8"?>
<vxml 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"
version="2.0" application="app-root.vxml">
<form id="say_goodbye">
<field name="answer">
<grammar type="application/srgs+xml" src="/grammars/boolean.grxml"/>
<prompt>Shall we say<value expr="application.bye"/>?</prompt>
<filled>
<if cond="answer">
<exit/>
</if>
<clear namelist="answer"/>
</filled>
</field>
</form>
</vxml>
|
|
在这个例子中,该应用的入口文件是leaf.vxml,因此leaf.vxml先被加载,它的application属性指定app-root.vxml为它的应用根文档,因此接着会加载app-root.vxml。在app-root.vxml中定义了一个变量――bye和一个link,只要用户说“operator”,那么控制就会跳转到operator-xfer.vxml。用户由名为say_goodbye的form开始人机交互。 |
|
C: Shall we say Ciao?
H: Si.
C: I did not understand what you said. (a platform-specific default message.)
C: Shall we say Ciao?
H: Ciao
C: I did not understand what you said.
H: Operator.
C: (Goes to operator_xfer.vxml, which transfers the caller to a human operator.)
|
|
注意,当用户在多文档的应用里,一次最多只有两个文档同时加载:应用根文档和一个应用叶文档,除非用户正在和应用根文档交互(此时只有一个文档加载,即应用根文档)。根文档的元素不能再指定application属性,而叶文档一定要指定application属性。在多文档的情况下,应用根文档总是被加载的,而应用叶文档则不一定。 |
|
应用根文档的绝对URI就是解释器当前的应用名(name of application)。如果有的话,该绝对URI可以包含一个查询字符串,但是它不可以包含段标识符。只要应用名没有变,解释器就一直在同一个应用里。一旦应用名改变,解释器就进入到一个新的应用里,并初始化新应用的根环境。应用根环境包括作用域为application的变量、语法、catch元素、script和property。 |
|
在用户会话期间,解释器从一个文档到另一个文档的跳转可以用<choice>、<goto>、<link>、<subdialog>和<submit>元素。有的跳转是在某个应用内,有的是在两个应用之间。根环境能否保存取决于跳转的类型: |
|
1、同一应用中根到叶的跳转 |
|
当当前文档为根文档,且目标文档的applocation属性值和当前应用的绝对URI一样,此时的跳转即为同一应用中根到叶的跳转。该应用根文档和它的环境都在跳转中保留下来。 |
|
2、同一应用中叶到叶的跳转 |
|
当当前文档为叶文档,且目标文档的application属性值和当前应用的绝对URI一样,此时的跳转即为同一应用中叶到叶的跳转,该应用根文档和它的环境都在跳转中保留下来。 |
|
3、同一应用中叶到根的跳转 |
|
当当前文档为叶文档,且目标文档的绝对URI和当前应用名一样,此时的跳转即为同一应用中叶到根的跳转。如果是通过<choice>或<goto>或<link>跳转的,该应用根文档和它的环境都在跳转中保留下来。如果是通过<submit>跳转的,根环境会被重新初始化,因为使用<submit>元素总是会导致URI的获取。 |
|
4、根到根的跳转 |
|
当当前文档为根文档,且目标文档也是根文档,即目标文档没有指定application属性,此时的跳转就是根到根的跳转。根环境和由缓存策略(caching policy)返回的应用根文档一起被初始化。 即使目标应用名和当前应用名一样,也可能会使用缓存策略。 |
|
5、子对话 |
|
当根文档或叶文档执行<subdialog>元素,此时即为subdialog调用。正如2.3.4节所讲的,subdialog调用时,创建了一个新的执行环境。在subdialog执行期间,应用根文档和它的环境都被保存起来,但是它们在被调用文档的执行环境里是不可用的。Subdialog新的执行环境有它自己的根环境,也许还有叶环境(leaf context)。当subdialog调用为一个非空的URI时,就可以用缓存策略来获得根和叶文档,以初始化新的根和叶环境。如果subdialog调用为一个空的URI,且为段标识符(例如“#sub1”),则根和叶文档保持不变,因此当前的根和叶文档会用来初始化新的根和叶环境。 |
|
6、应用之间的跳转 |
|
两个应用间的跳转使得应用根环境和下一个应用的根文档一起被初始化。 |
|
如果某个文档引用了一个不存在的应用根文档,则抛出一个error.badfetch事件。如果某个文档的application属性引用的文档也指定了application属性,则抛出一个error.semantic事件。 |
|
下面的图表说明了根和叶文档在应用根环境中跳转的结果。在这个图表中,方框表示文档,方框内纹理的变化表示根环境的初始化,实心箭头表示跳转到箭头所指向的URI,竖向的虚线箭头所指向的表示application属性指定的URI。 |
|
|
图3: 保存根环境的的跳转 |
|
在这个图中,所有的文档都属于同一应用。1、2、3、4表示跳转的步骤: |
|
1、跳转经由URI A到文档1,应用环境被初始化。假设文档1是这个会话的入口文件。当前的应用名为A。 |
|
2、文档1指定了一个跳转经由URI B到文档2。文档2的application属性值等于A。根文档为文档1,和它的环境一起被保存下来。这是在同一应用中,根到叶的跳转。 |
|
3、文档2指定了一个跳转经由URI C到另外一个叶文档,即文档3。文档3的application属性值也等于A。根文档和它的环境一起被保存下来。这是在同一应用中,叶到叶的跳转。 |
|
4、文档3指定了一个跳转经由URI A到文档1,它是通过或 或来跳转的。文档1的根环境原封不动。这是在同一应用中。叶到根的跳转。 |
|
下面的图说明了初始化根环境的跳转。 |
|
|
图4: 初始化根环境的跳转 |
|
5、文档1指定了一个跳转经由URI A到文档4。文档4没有指定它的application属性,它自己就是根文档,它的根环境被初始化。这是一个根到根的跳转。 |
|
6、文档4指定了一个跳转经由URI D到文档5。文档5的application属性值为URI E和当前的应用名不一样。解释器进入到一个新的应用。URI E指向了文档6。根环境根据文档6的内容来初始化。这就是应用之间的跳转。 |
|
7、文档5指定了一个跳转经由URI A。 |
|
三、子对话 |
|
subdialog是一种用来分解复杂的对话序列并改善他们的结构,或创建可重用组件的一种机制。例如帐户信息请求可能包含了几部分信息的收集,像帐户号和家庭电话号码。这样的客户管理服务可以由几个应用组成 这些应用能够共享基本的组件,因此用subdialog来构造这个服务就很合理。下面的例子说明了这一点。第一个文档(app.vxml)用来调整用户的帐号,因此,它必须得到帐户的信息和要调整的级别。通过subdialog元素调用另一个VoiceXML文档让用户输入以获得帐户信息。当第二个文档执行的时候,第一个文档的dialog被挂起,等待信息的返回。第二个文档通过<return>元素返回用户交互的结果,返回值可以通过<subdialog>元素的name属性定义的变量获得。 |
|
用户服务程序(app.vxml): |
|
<?xml version="1.0" encoding="UTF-8"?>
<vxml 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"
version="2.0">
<form id="billing_adjustment">
<var name="account_number"/>
<var name="home_phone"/>
<subdialog name="accountinfo" src="acct_info.vxml#basic">
<filled>
<!-- Note the variable defined by "accountinfo" is
returned as an ECMAScript object and it contain
two properties defined by the variables specified in the
"return" element of the subdialog. -->
<assign name="account_number" expr="accountinfo.acctnum"/>
<assign name="home_phone" expr="accountinfo.acctphone"/>
</filled>
</subdialog>
<field name="adjustment_amount">
<grammar type="application/srgs+xml" src="/grammars/currency.grxml"/>
<prompt>
What is the value of your account adjustment?
</prompt>
<filled>
<submit next="/cgi-bin/updateaccount"/>
</filled>
<field>
<form>
</vxml>
|
|
包含帐户信息的subdialog文档(acct_info.vxml): |
|
<?xml version="1.0" encoding="UTF-8"?>
<vxml 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"
version="2.0">
<form id="basic">
<field name="acctnum">
<grammar type="application/srgs+xml" src="/grammars/digits.grxml"/>
<prompt>What is your account number?</prompt>
<filled>
<field name="acctphone">
<grammar type="application/srgs+xml" src="/grammars/phone_numbers.grxml"/>
<prompt>What is your home telephone number?</prompt>
<filled>
<!-- The values obtained by the two fields are supplied
to the calling dialog by the "return" element. -->
<return namelist="acctnum acctphone"/>
</filled>
</field>
</form>
</vxml>
|
|
subdialog被调用的时候产生了一个新的执行环境,subdialog既可以是现有文档的一个新dialog,也可以是新文档的一个新dialog。 |
|
Subdialog可以由几个文档组成。图5展示了一系列文档(D)跳转到subdialog(SD),并返回的执行流程。 |
|
|
图5: subdialog由几个文档组成,并由最后一个subdialog返回 |
|
当dialog D2调用文档sd2.vxml中的subdialog SD1时,dialog D2的执行环境被挂起,sd1指定执行跳转到sd2.vxml中的dialog(使用<goto>元素)。因此,当sd2.vxml中的dialog返回时,控制直接返回到dialog D2。 |
|
图6展示了一个多文档的subdialog调用的例子,在这个例子中,控制由一个subdialog跳转到另一个subdialog。 |
|
|
图6: 由几个文档组成的subdialog,并由第一个subdialog返回 |
|
在sd1.vxml中的subdialog指定将控制跳转到sd2.vxml的另一个subdialog,SD2。当执行SD2时,总共有两个环境被挂起:D2的dialog环境因等待SD1返回而挂起,SD1的dialog因等待SD2返回而挂起。当SD2返回时,控制也返回到SD1,SD1又把控制返回到dialog D2。 |
|
四、后期处理 |
|
在某些情况下(特别是当VoiceXML解释器处理挂机事件时),解释器可以继续执行,进入后期处理状态,即使解释器和终端用户之间的连接已经中断。后期处理的目的是让VoiceXML应用去完成一些必要的后期的清理。例如提交信息到应用服务器。举个例子来说,下面的<catch>元素将捕获connection.disconnect.hangup事件,并进入后期处理状态执行。 |
|
<catch event="connection.disconnect.hangup">
<submit namelist="myExit" next="http://mysite/exit.jsp"/>
</catch>
|
|
然而,在后期处理状态,应用必须保持跳转状态,且不可以进入等待状态(详见4.1.8节),因此,在后期处理状态,应用不应该进入<field>或<record>或<transfer>。如果此时VoiceXML应用试图进入等待状态,VoiceXML解释器必须退出。 |
|
除了上面的限制,VoiceXML应用的执行可以在后期处理状态正常继续。例如,应用在后期处理状态可在文档之间跳转,如果没有任何符合条件的form item被选定的话(详见2.1.1节),解释器必须退出。 |