| 元素 |
CCXML 语言是 XML 的一个流派,提供了专门的呼叫控制功能。和所有的 XML 变体一样,CCXML 也由一组特定的元素组成:
- 元素包括起始标签和结束标签,元素的内容在两个标签之间。属性信息可以放在内容中或者起始标签之内。比如,
<accept connectionid="evt.connectionid"/>。 - 空元素只包括起始标签和结束标签,中间没有内容。比如
<eventprocessor></eventprocessor>。
完整的 CCXML 元素列表,请参考http://docs.voxeo.com/ccxml/1.0/frame.jsp?page=t_8ccxml.htm
| 事件 |
如 CCXML 概述 中所述,CCXML 语言是事件驱动的。现在我们来介绍事件驱动的概念,我们可以把事件看作:
- 事件可以是电话系统发送给 CCXML 应用程序并驱动 CCXML 应用程序执行相应呼叫控制功能的消息。
- 事件也可以由用户编写的 CCXML 应用程序、CCXML Interpreter 或者被 CCXML Interpreter 调用的 Voice 应用程序生成。
| 事件处理 |
所谓"处理",我们指的是事件可以发出和接收。事件处理可以使用 <eventprocessor> 和 <transition> 元素完成。等待处理的事件保存在一个队列中。<eventprocessor> 元素从队列中取出的一个可用的事件。
<eventprocessor> 元素包含一个或多个 <transition> 元素。每个 <transition> 元素被设计成能识别一个特定的事件,并包含和那个事件相适应的信息。当 <transition> 元素处理一个事件时。应用程序运行包含在起始标签和结束标签之间的代码。
下面是一个简单的例子,说明 <eventprocessor> 和 <transition> 元素的用法:
<eventprocessor> <!-- Transition for event A --> <transition> some code to be run for event A </transition> <!-- Transition for event B --> <transition> some code to be run for event B </transition> </eventprocessor> 该例中,到达第一个 </transition> 标签时,如果事件 B 是队列中的下一个事件,应用程序将直接移动到第二个 <transition> 元素。
| ECMAScript |
ECMAScript 和 JavaScript 类似,是 CCXML 内使用的脚本语言。
CCXML 中所用的 ECMAScript 规范是基于 ECMAScript Compact Profile(ES-CP,也成为 ECMA-327)的一种工业标准脚本语言。ES-CP 是为资源受限的设备量身定做的,CCXML 选择它是为了提高效率(请参阅 参考资料)。
ECMA 变量用于保存事件的信息。这些信息可供处理事件的 <transition> 使用。关于 ECMA 变量的例子和更多信息,请参阅 事件属性。
| <xml> 和 <ccxml> 元素 |
所有 CCXML 脚本中的第一个元素都是 <xml>,它指明脚本所使用 version 和 encoding:
<?xml version="1.0" encoding="UTF-8"?> 这个声明之后是 CCXML 代码。整个 CCXML 脚本都放在 <ccxml> 元素中,它是 CCXML 脚本的父元素。
<?xml version="1.0" encoding="UTF-8"?> <ccxml version="1.0"> <eventprocessor> <!-- Accepts an incoming call --> <transition event="connection.ALERTING" name="evt"> <accept connectionid="evt.connectionid"/> </transition> <!-- Starts VoiceXML dialog on a separate thread --> <transition event="connection.CONNECTED" name="evt"> <dialogstart src="'welcome.vxml'" type="application/xml+vxml"/> </transition> <!-- Disconnects the call when dialog finishes --> <transition event="dialog.exit" name="evt"> <disconnect connectionid="evt.connectionid"/> </transition> </eventprocessor> </ccxml> 紧跟在起始标签 <ccxml> 元素之后可以放上一些初始化代码。执行完这些初始化代码之后,将继续执行 <eventprocessor> 元素。
| <eventprocessor> 和 <transition> 元素 |
每个 CCXML 脚本都需要一个 <eventprocessor> 元素,它控制脚本的执行。<eventprocessor> 还负责处理事件队列:从队列中去掉第一个事件并选择要执行的转换。
<transition> 元素定义了根据收到的事件要执行哪一个 CCXML 元素。<transition> 元素都包含在 <eventprocessor> 元素内。
下面的例子说明了如何处理即将到来的呼叫。从即将到来的呼叫收到的第一个事件是 connection.ALERTING 事件。当连接转换到警告状态时将生成这个事件。收到该事件时,将使用 <accept> 元素接受这次呼叫。
<?xml version="1.0" encoding="UTF-8"?> <ccxml version="1.0"> <eventprocessor> <!-- Accepts an incoming call --> <transition event="connection.ALERTING" name="evt"> <accept connectionid="evt.connectionid"/> </transition> </eventprocessor> </ccxml> <transition> 元素的 event 属性表明哪一个事件将导致 <transition> 的执行。上例中,在接收到 connection.ALERTING 事件时选择 <transition>。<transition> 元素中的 name 属性是一个 ECMA 变量,事件的细节将保存在其中。
在 <transition> 元素内部,子元素按照脚本的顺序执行,因此首先执行的是 <accept> 元素。这个元素将导致电话系统层连接该呼叫。属性 connectionid 必须包含即将到来的呼叫所标记的连接标识符。该例中,这个标记包含在 connection.ALERTING 事件的形式中。您可以从这个事件获得关于连接的信息。
| 事件属性 |
所有的事件都有属性,在 W3C 规范中可以找到完整的属性列表。比如, connection.ALERTING 事件包含关于事件的 name、connectionid、protocol、info attribute 和 connection 属性的信息。connection 属性提供了到连接对象的访问,其中包含关于连接的更多信息。
访问这些信息可以使用下面的符号:
evt.nameevt.connectionidevt.protocol
其中 evt 是在 <transition> 元素中为事件指定的名字。
这样就可以从 connection.ALERTING 事件中获得 connectionid,然后使用这个 ID 告诉 <accept> 元素接受哪次呼叫。
<!-- Accepts an incoming call --> <transition event="connection.ALERTING" name="evt"> <accept connectionid="evt.connectionid"/> </transition> | <dialogstart> 元素 |
如果 <accept> 元素成功完成,那么接下来生成的事件就是 connection.CONNECTED。这个事件可以采用类似的方式处理,但是在这个 <transition> 中将在这次呼叫上启动一个对话。
<transition event="connection.CONNECTED" name="evt"> <dialogstart src="'welcome.vxml'" type="application/xml+vxml"/> </transition> 这里采用了同样的符号处理 connection.CONNECTED 事件。<dialogstart> 元素在 <transition> 元素内部使用。<dialogstart> 元素将启动一个对话,对话和这个特定的连接关联。
使用 src 属性指定初始对话文档的 URI 引用。这是将加载并执行的对话文档。src 属性必须等于一个返回字符串的 ECMAScript 表达式。包围"welcome.vxml"的单引号表示这是一个字符串。如果省略单引号,则"welcome.vxml"被解释成一个变量并尝试对这个变量求值从而造成错误。也可以使用变量包含文档 URI。
新的对话在单独的逻辑线程上执行,因此对话的执行不会阻塞 CCXML 会话中其他事件的处理。
<dialogstart> 元素中还指定了 type 属性。这是文档的 MIME 类型,该例中使用的是 VoiceXML 文档并制定了相应的 MIME 类型。。
| <disconnect> 元素 |
对话完成时,CCXML 会话接受一个 dialog.exit 事件。这个时候可以断开呼叫连接。
<transition event="dialog.exit" name="evt"> <disconnect connectionid="evt.connectionid"/> </transition> <disconnect> 元素中必须指定要断开的连接的 connectionid。这个信息从 dialog.exit 事件中检索,以保证对话刚刚完成的呼叫就是要断开连接的呼叫。
| basic.xml 的完整代码 |
第一个例子中的所有元素现在已经讨论完了。您所创建的 CCXML 脚本可以应答呼叫、向呼叫方播放对话,然后在对话结束时断开呼叫连接。
下面是完整的代码:
<?xml version="1.0" encoding="UTF-8"?> <ccxml version="1.0"> <eventprocessor> <!-- Accepts an incoming call --> <transition event="connection.ALERTING" name="evt"> <accept connectionid="evt.connectionid"/> </transition> <!-- Starts VoiceXML dialog on a separate thread --> <transition event="connection.CONNECTED" name="evt"> <dialogstart src="'welcome.vxml'" type="application/xml+vxml"/> </transition> <!-- Disconnects the call when dialog finishes --> <transition event="dialog.exit" name="evt"> <disconnect connectionid="evt.connectionid"/> </transition> </eventprocessor> </ccxml>
| 变量 |
在 CCXML 脚本中有两种不同的变量作用域――全局作用域和转换作用域:
全局作用域:全局作用域的变量必须在 <ccxml> 元素之后、<eventprocessor> 元素之前声明。这些变量可以在 CCXML 脚本中的任何地方引用或者修改。当加载脚本时全局变量就存在了,并且仅在脚本内部可见。
转换作用域: 这种作用域的变量声明在 <transition> 内而且只能在该转换的内部和生命期内使用。
对于该例而言,为了记录不同号码的呼叫次数必须声明全局变量。
<?xml version="1.0" encoding="UTF-8"?> <ccxml version="1.0"> <var name="count1001" expr="0"/> <var name="count1002" expr="0"/> <var name="countother" expr="0"/> <eventprocessor> <!-- Accepts an incoming call --> <transition event="connection.ALERTING" name="evt"> <accept connectionid="evt.connectionid"/> </transition> <!-- Starts VoiceXML dialog on a separate thread --> <transition event="connection.CONNECTED" name="evt"> <dialogstart src="'welcome.vxml'" type="application/xml+vxml"/> </transition> <!-- Disconnects the call when dialog finishes --> <transition event="dialog.exit" name="evt"> <disconnect connectionid="evt.connectionid"/> </transition> </eventprocessor> </ccxml> name 属性用于指定变量的名称。
expr 属性是该变量的初始值。expr 可以是任何有效的 ECMAScript 表达式,是一个可选属性。
该例中这个值被初始化为 0。
| 错误处理 |
随着 CCXML 脚本越来越复杂,检查错误可能变得非常困难。如果 CCXML 代码中出现错误,就会造成错误事件该脚本中包含一个通配符 <transition> 来处理这些事件。
CCXML Interpreter 按照脚本顺序选择第一个符合特定事件的 <transition>。把通配符 <transition> 放在 CCXML 脚本的最后,这样就不会中断预期的脚本流。
因此,例子中的最后一个 <transition> 应该是:
<transition event="error.*" name="evt"> <log expr="evt.name + ' occurred, with error condition ' + evt.error"/> </transition> event 属性使用了星号(*)形式的通配符,因此该 <transition> 元素和任何以"error."开始的事件匹配。比如,当 CCXML 脚本包含语义错误时将生成 error.ccxml。
<log> 元素的结果输出到文件 ccxml_app_log 中,这个文件可以在 IBM alphaWorks 的 var/dirTalk/DTBE/native/aix 目录下找到。<log> 元素的 expr 属性是一个结果为字符串的 ECMAScript 表达式。因此可以计算表达式的值并记录结果。上例中,事件的 error 属性和事件 name 都被记录下来。
| 逻辑元素 |
要计算呼叫的次数,需要修改 connection.CONNECTED <transition> 使其包含逻辑元素。
注意,welcome.vxml 对话对所有的呼叫方播放,无论拨的什么号码。这种功能是通过 <dialogstart> 元素实现的,如 <dialogstart> 元素 中所述。因为对话在单独的线程中执行,可以继续执行使用逻辑计算呼叫次数的 CCXML 脚本。
下面的 <transition> 说明如何使用逻辑统计不同号码的呼叫次数。
<transition event="connection.CONNECTED" name="evt"> <dialogstart src="'welcome.vxml'" type="application/xml+vxml"/> <if cond="evt.connection.local=='1001'"> <assign name="count1001" expr="count1001 + 1"/> <log expr="'Call number ' +count1001+ ' to 1001'"/> <elseif cond="evt.connection.local=='1002'"> <assign name="count1002" expr="count1002 + 1"/> <log expr="'Call number ' +count1002+ ' to 1002'"/> </elseif> <else> <assign name="countother" expr="countother + 1"/> <log expr="'Call number ' +countother+ ' to another number - ' +evt.connection.local"/> </else> </if> </transition> 每个连接事件都包含一个连接对象,这个对象包含一些不同的参数,其中之一是本地号码(呼叫方拨入的号码)。这个值通过计算 evt.connection.local 得到,其中 evt 是 <transition> 中给定事件的名称。该例中使用 evt.connection.local 确定呼叫的号码和要增加的变量。
这里使用 <var> 创建了三个变量。这些变量的值可以使用 <assign> 元素修改,这个元素和 <var> 非常相似。name 属性指定了变量的名称, expr 属性决定了变量的新值。
<if> 和 <elseif> 元素都有一个 cond 属性。它是结果为真或者假的 ECMAScript 表达式:
- 当
evt.connection.local的值是 1001 时,<if>的结果为真。这样将执行<if>元素之后,<elseif>、<else>或者</if>之前的所有元素。在上例中,<assign>是第一个要执行的元素,它向全局变量count1001加 1。count1001的新值也被记录下来。 - 如果
evt.connection.local的结果不是 1001,则测试<elseif>。如果拨入的号码是 1002,则执行包含在<elseif>标签中的语句。 - 如果
<if>和<elseif>的条件都不成立,则执行<else>元素。该例中向countother变量加 1,并把拨入的号码记录下来。
逻辑能够分别统计不同号码的呼叫次数,因此提供了简单的投票应用程序所需要的功能。所有的呼叫方都听到同样的对话,但拨入的号码分别计算。
注意:IBM alphaWorks 所提供的 <if>、<elseif> 和 <else> 元素的实现不同于 W3C Last Call Working Draft 规范。
| 事件捕获 |
在讨论错误处理时我们介绍了通配符转换的概念,以用于捕获错误事件。可以使用同样的方法来捕获未规定相应 <transition> 的其他事件。
通过在脚本底部输入以通配符作为事件 name 的 <transition>,可以捕获任何未被特定 <transition> 捕获的事件。然后可以使用该 <transition> 中的记录检查事件。开发 CCXML 应用程序时,为了保证所有必要的事件都处理了,这种技术非常有用。
下面的 <transition> 将记录所有未处理的事件:
<transition event="*" name="evt"> <log expr="evt.name + ' event was caught'"/> </transition> 您可以在 CCXML 脚本中放入空转换,以避免某些事件触发通配符 <transition>。下面的事件都是投票程序生成的:
<transition event="ccxml.loaded"/> <transition event="dialog.started"/> <transition event="connection.DISCONNECTED"/> ccxml.loaded事件是当启动新的 CCXML 会话并且 CCXML 脚本已经加载的时候生成的。通过该事件可以在不呼叫的情况下启动执行。dialog.started事件是当一个<dialogstart>元素成功完成时生成的。connection.DISCONNECTED事件是在<disconnect>成功执行之后,或者平台已经断开连接发出通知时生成的。
除了 voting.ccxml 例子中的这些转换,任何没有预料到的事件都会被捕获并记录。
| 完整的代码 |
这一节介绍了 CCXML 变量、错误处理、事件捕获和逻辑元素。voting.ccxml 应用程序统计不同电话号码的呼叫次数。
下面是完整的代码:
<?xml version="1.0" encoding="UTF-8"?> <ccxml version="1.0"> <!-- Global variable to store the number of calls to 1001 --> <var name="count1001" expr="0"/> <var name="count1002" expr="0"/> <var name="countother" expr="0"/> <eventprocessor> <transition event="ccxml.loaded"/> <!-- Accepts an incoming call --> <transition event="connection.ALERTING" name="evt"> <accept connectionid="evt.connectionid"/> </transition> <!-- Starts VoiceXML dialog on a separate thread --> <!-- Counts the number of times 1001 and 1002 are dialed --> <!-- Logs the dialed number --> <transition event="connection.CONNECTED" name="evt"> <dialogstart src="'welcome.vxml'" type="application/xml+vxml"/> <if cond="evt.connection.local=='1001'"> <assign name="count1001" expr="count1001 + 1"/> <log expr="'Call number ' +count1001+ ' to 1001'"/> <elseif cond="evt.connection.local=='1002'"> <assign name="count1002" expr="count1002 + 1"/> <log expr="'Call number ' +count1002+ ' to 1002'"/> </elseif> <else> <assign name="countother" expr="countother + 1"/> <log expr="'Call number ' +countother+ ' to another number - ' +evt.connection.local"/> </else> </if> </transition> <transition event="dialog.started"/> <!-- Disconnects the call when dialog finishes --> <transition event="dialog.exit" name="evt"> <disconnect connectionid="evt.connectionid"/> </transition> <transition event="connection.DISCONNECTED"/> <!-- Catches any errors --> <transition event="error.*" name="evt"> <log expr="evt.name + ' occurred, with error condition ' + evt.error"/> </transition> <!-- Catches any missed events --> <transition event="*" name="evt"> <log expr="evt.name + ' event was caught'"/> </transition> </eventprocessor> </ccxml>
| 设置计时器 |
第一步是使用 <send> 元素的 delay 属性设置一个计时器。<send> 元素用于向另一个 CCXML Interpreter 或者外部事件处理程序发送消息。对于这个练习而言,就是向当前会话发送一个事件。 一旦载入当前脚本必须马上设置计时器,因此发送必须在 ccxml.loaded 转换中完成。
修改 basic.ccxml 的代码包含以下内容:
<transition event="ccxml.loaded" name="evt"> <send target="evt.sessionid" data="'refresh'" delay="43200s"/> </transition> target确定事件要发送给谁,该例中即当前会话。data属性指定要发送的事件名。delay属性用于指定一个 12 小时的时间段,时间格式为 CSS2 格式。
现在建立并准备好了计时器,<send> 元素立即返回 send.SEND_SUCCESSFUL 事件。但是,refresh 事件直到下一个定时器触发时刻才会分派。
为了处理 send.SEND_SUCCESSFUL 事件需要增加另一个转换。可以是空白 <transition> 元素:
<transition event="send.SEND_SUCCESSFUL" name="evt"/> | 修改脚本 |
为了处理 refresh 事件,向脚本中增加一个新的 <transition>:
<transition event="refresh" name="evt"> <fetch next="'voting.ccxml'"/> </transition> 当定时器终止时将发送 refresh 事件。遇到该事件时将执行上面 <transition>。现在必须更新执行的 CCXML 脚本,使呼叫方能够开始投票。
改变脚本需要两个阶段:
- 取得新的脚本
- 使新的脚本能够执行
取得新的脚本
您需要发出一个 <fetch> 请求。该元素将查找、加载和解析新的 CCXML 脚本。
<fetch> 元素的 next 属性使用 ECMAScript 表达式指定要取得的脚本的 URI。该例中将取得 voting.ccxml。注意 voting.ccxml 脚本是异步取得的,因此队列中的下一个事件将继续处理。
党 <fetch> 成功完成时, fetch.done 事件被添加到队列中。
使新的脚本能够执行
现在要执行 <goto>。<goto> 可以在 <fetch> 元素完成后执行。需要如下所示的 <transition>:
<transition event="fetch.done" name="evt"> <goto fetchid="evt.fetchid"/> </transition> <goto> 把会话控制转移到 fetchid 标识的脚本。目标脚本代替了当前脚本,所有和当前脚本有关的事件源都被目标脚本继承。然后结束当前脚本的执行。
| 投票程序中需要做的修改 |
修改 voting.ccxml 应用程序使其具有和修改后的 basic.ccxml 应用程序类似的功能。在 voting.ccxml 中修改 ccxml.loaded <transition> 使其和 basic.ccxml 中的 ccxml.loaded <transition> 相同。然后添加以下两个转换:
<transition event="refresh" name="evt"> <fetch next="'basic.ccxml'"/> </transition> <transition event="fetch.done" name="evt"> <goto fetchid="evt.fetchid"/> </transition> 当修改后的 basic.ccxml 和 voting.ccxml 运行时,呼叫方在一天中的任何时候都听到同样的对话。但是,呼叫方的投票只在一天中的 12 个小时内统计。
该例中还没有指定哪 12 个小时内计算投票。当收到 ccxml.loaded 事件时作为这个时间段的开始。
No comments:
Post a Comment