元素 |
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.name
evt.connectionid
evt.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