Monday, April 23, 2007

CCXML语言

CCXML语言
 
元素 第 1 页(共4 页)


CCXML 语言是 XML 的一个流派,提供了专门的呼叫控制功能。和所有的 XML 变体一样,CCXML 也由一组特定的元素组成:

  • 元素包括起始标签和结束标签,元素的内容在两个标签之间。属性信息可以放在内容中或者起始标签之内。比如,<accept connectionid="evt.connectionid"/>
  • 空元素只包括起始标签和结束标签,中间没有内容。比如 <eventprocessor></eventprocessor>

完整的 CCXML 元素列表,请参考http://docs.voxeo.com/ccxml/1.0/frame.jsp?page=t_8ccxml.htm

 


事件 第 2 页(共4 页)


CCXML 概述 中所述,CCXML 语言是事件驱动的。现在我们来介绍事件驱动的概念,我们可以把事件看作:

  • 事件可以是电话系统发送给 CCXML 应用程序并驱动 CCXML 应用程序执行相应呼叫控制功能的消息。
  • 事件也可以由用户编写的 CCXML 应用程序、CCXML Interpreter 或者被 CCXML Interpreter 调用的 Voice 应用程序生成。


事件处理 第 3 页(共4 页)


所谓"处理",我们指的是事件可以发出和接收。事件处理可以使用 <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 第 4 页(共4 页)


ECMAScript 和 JavaScript 类似,是 CCXML 内使用的脚本语言。

CCXML 中所用的 ECMAScript 规范是基于 ECMAScript Compact Profile(ES-CP,也成为 ECMA-327)的一种工业标准脚本语言。ES-CP 是为资源受限的设备量身定做的,CCXML 选择它是为了提高效率(请参阅 参考资料)。

ECMA 变量用于保存事件的信息。这些信息可供处理事件的 <transition> 使用。关于 ECMA 变量的例子和更多信息,请参阅 事件属性

 

 

 

 

 

 

 

 

 

 

 

<xml> 和 <ccxml> 元素 第 2 页(共7 页)


所有 CCXML 脚本中的第一个元素都是 <xml>,它指明脚本所使用 versionencoding

<?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> 元素 第 3 页(共7 页)


每个 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 事件的形式中。您可以从这个事件获得关于连接的信息。

事件属性 第 4 页(共7 页)


所有的事件都有属性,在 W3C 规范中可以找到完整的属性列表。比如, connection.ALERTING 事件包含关于事件的 nameconnectionidprotocolinfo attributeconnection 属性的信息。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> 元素 第 5 页(共7 页)


如果 <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> 元素 第 6 页(共7 页)


对话完成时,CCXML 会话接受一个 dialog.exit 事件。这个时候可以断开呼叫连接。

   <transition event="dialog.exit" name="evt">       <disconnect connectionid="evt.connectionid"/>    </transition> 

<disconnect> 元素中必须指定要断开的连接的 connectionid。这个信息从 dialog.exit 事件中检索,以保证对话刚刚完成的呼叫就是要断开连接的呼叫。

 

 

basic.xml 的完整代码 第 7 页(共7 页)


第一个例子中的所有元素现在已经讨论完了。您所创建的 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> 


 

 

 

 

 

变量 第 2 页(共6 页)


在 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。

 

 

错误处理 第 3 页(共6 页)


随着 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 都被记录下来。

 

 

逻辑元素 第 4 页(共6 页)


要计算呼叫的次数,需要修改 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 规范。

 

事件捕获 第 5 页(共6 页)


在讨论错误处理时我们介绍了通配符转换的概念,以用于捕获错误事件。可以使用同样的方法来捕获未规定相应 <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 例子中的这些转换,任何没有预料到的事件都会被捕获并记录。

 

 

 

完整的代码 第 6 页(共6 页)


这一节介绍了 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> 


 

设置计时器 第 2 页(共6 页)


第一步是使用 <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"/>  
 
修改脚本第 3 页(共6 页)


为了处理 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 标识的脚本。目标脚本代替了当前脚本,所有和当前脚本有关的事件源都被目标脚本继承。然后结束当前脚本的执行。

 

 

投票程序中需要做的修改第 4 页(共6 页)


修改 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.ccxmlvoting.ccxml 运行时,呼叫方在一天中的任何时候都听到同样的对话。但是,呼叫方的投票只在一天中的 12 个小时内统计。

该例中还没有指定哪 12 个小时内计算投票。当收到 ccxml.loaded 事件时作为这个时间段的开始。

 

 

 


No comments: