内容


支持语音的 XML,第 2 部分

开发支持语音的日程表

创建自己的个人数字秘书管理日程安排

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 支持语音的 XML,第 2 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:支持语音的 XML,第 2 部分

敬请期待该系列的后续内容。

简介

任何人在工作繁忙的时候都需要一个良好的日程表工具,那么为什么不考虑设计一个支持语音的日程表工具呢?在 VoiceXML 的帮助下,我们可以创建一个能通过说话操纵的日程表。在创建该工具的过程中,我们还将学习如何:

  • 创建一个基于菜单的应用程序
  • 接收输入
  • 将输入写入脚本,用于进一步处理
  • 读取数据文件并输出 VXML

关于本系列

语音和音频在 Web 上的应用日益盛行。具体的例子包括目前的各种在线音乐和网络广播。本系列将结合语音和 XML 开发以下这些实用的应用程序:

日程表工作流

日程表工作流将使用一个非常简单的结构,至少在最初只有以下两个选项:

  • 列出已有约会
  • 添加约会

为方便起见,我们还将提供一个不需要特殊类型处理程序的选项。当用户说出 “finish” 时,调用将断开连接。

图 1 显示了主菜单的基本布局,共含有两个选项。

图 1. 应用程序的主菜单
应用程序的主菜单
应用程序的主菜单

图 1 中不难看出,该程序系统相对较为简单。如果您说出 “diary”,那么应用程序将输出当天的所有日程安排。如果您说出 “appointment”,那么应用程序将开始接收输入。

存储日程表信息使用的是 XML 文档,这意味着我们需要能够动态地将 XML 文档作为 VXML 输出。要接收约会信息 — 也就是年月日和约会类型 — 我们可以在 VXML 中建立其整体模型,不过我们还需要一个动态的组件来保存输入。

第二个选项的结构本身是十分简单的;它需要从用户那里接收 — 通过语音或 Dual Tone Multi-Frequency (DTMF) — 日期和时间信息。图 2 显示了其结构的详细信息。

图 2. 接收约会选择
接收约会选择
接收约会选择

接下来,我们将讨论该系统的具体实现。

日程表主菜单

日程表主菜单是一个简单的 VXML 文件,它所提供的主要选项将作为应用程序其余部分的开始点。要实现日程表主菜单,我们可以采用多种方式。但是就本例而言,我们将使用选项标记。这种方法可以自动设置关联字段的值,并且将从话筒(Phone pad)接收一个词用于语音识别,和一个 DTMF 音调。

这样,用户可以通过语音命令或使用小键盘进行选择,因此我们必需提供一个提示,用于说出可用选项的组合。

使用用户选择的内容填充字段后,if 结构将做出判断并执行下一步操作。就前两个选项而言,这意味着链接到不同的 URL。对于最后一个选项,断开标记将挂断电话。

清单 1 显示了 VXML 结构的主菜单。

清单 1. Calendar 应用程序的主菜单
<?xml version="1.0" encoding ="UTF-8"?>

<!DOCTYPE vxml PUBLIC "-//W3C//DTD VOICEXML 2.1//EN" 
             "http://www.w3.org/TR/voicexml21/vxml.dtd">

<vxml version="2.1">

  <form id="MM"> 
    <field name="FMM"> 
      <prompt> 
        Please choose.
        <break strength="medium"/> 
        Press one or say diary for your current diary, 
        press two or say appointment to add a new appointment.
        Say finish if you want to finish the session.
      </prompt> 

      <option value="diary" dtmf="1"> 
        diary
      </option> 

      <option value="appointment" dtmf="2"> 
        appointment
      </option> 

      <option value="finish" dtmf="0"> 
        finish
      </option> 
 

      <filled> 
        <if cond="FMM =='diary'">
          <goto next="dumpdate.cgi"/>
        <elseif cond="FMM =='appointment'"/>
          <goto next="entry.vxml"/>
        <elseif cond="FMM =='save'"/>
          <goto next="savedate.cgi"/>
        <elseif cond="FMM =='finish'"/>
          <prompt>Thank you</prompt>
          <disconnect/>
        </if>
      </filled> 

    </field> 
  </form>
</vxml>

这里还没有涉及用户的身份。但是在企业环境中,我们需要添加一个身份验证级别,让用户输入惟一标识号码(或者说是他的名称)和一个 PIN 号码或密码用于验证条目。然后,我们可以在 VoiceXML 和动态脚本之间传递 ID 号码。

还有,虽然我们将使用 XML 作为信息的存储格式,但如果您拥有一个惟一可用的 ID,那么可以更加方便地使用这个 ID 直接从数据库中提取信息。

将这些考虑放在一边,我们只研究如何使用 VXML 接收新约会条目。

添加条目

要在已有文件中添加一个约会,我们需要先从用户处获得要添加条目的日期、时间和约会类型信息。

同样,我们可以采用多种方法接收信息。但是对于前 5 个字段(日、月、年、小时、分钟),我们最终需要获取的是数字信息。

字段标记将接收一个 type 属性,该属性指定了要存储信息的类型,并且还可以用于指定接收操作所使用的语法规则。一些输入类型是预先定义的,数值输入(数字)输入类型就是其中之一,该类型将接收最大和最小数值的指定:<field name="Day" type="digits?minlength=1;maxlength=2">

注意,这些值是数字值,而不是号码,因此用户必须说数字作为输入。尽管这种方式缺乏一些灵活性,但是它可以确保用户获得正确的信息,特别是在指定年时尤为明显,因为年有很多种说法。

我们可以重复这一过程,完成所有 5 个数值的定义,如清单 2 所示。对于各个字段定义,嵌入的提示将被读出,并且系统将等待适当的响应,然后再移至下一个字段。大多数 VoiceXML 浏览器都含有一些自动化的元素,如果输入内容与预期数据类型不匹配时这些元素将重新弹出提示。

清单 2. 接收输入值
<?xml version="1.0" encoding ="UTF-8"?>

<!DOCTYPE vxml PUBLIC "-//W3C//DTD VOICEXML 2.1//EN" 
            "http://www.w3.org/TR/voicexml21/vxml.dtd">

<vxml version="2.1" xmlns="http://www/w3/org/2001/vxml" xml:lang="en-US">

<form id="MyForm">
<prompt>Adding a new appointment.<break time="1000"/></prompt>
<field name="Day" type="digits?minlength=1;maxlength=2">
<prompt>Say the day of the month (using digits). For example, for 12th say one, 
two.</prompt>
</field>

<field name="Month" type="digits?minlength=1;maxlength=2">
<prompt>Say the month (in numbers)</prompt>
</field>

<field name="Year" type="digits?minlength=4;maxlength=4">
<prompt>Say the year (in numbers, using four digits)</prompt>
</field>

<field name="Hour" type="digits?minlength=1;maxlength=2">
<prompt>Say the hour (using the 24-hour clock)</prompt>
</field>

<field name="Minute" type="digits?minlength=1;maxlength=2">
<prompt>Say the minutes</prompt>
</field>

对于会议的类型,我们需要专门指定一个接收词或短语列表。这样,当用户说出这些词的时候,浏览器便可以识别它们。我们可以从以下两种不同的标准和方法中选择一种来指定语法规则:一种是根据文本格式,另一种是 XML 规范。

我们可以方便地将文本规范嵌入到 VXML 文档中,方法是使用一个 CDATA 块。该格式让我们能够指定准许的词的发音,以及如何解释它们并在 VoiceXML 字段中将它们呈现为文本或其他字符串。比如说,如果我们想要接收 “meeting” 这个语音并将相应的词指派给该字段,那么可以使用这个方法:[meeting] {<TypeOfMeeting "meeting">}

我们还可以添加一些额外的选项,如清单 3 所示。其中提供了牙科医生、医生和聚会选项。

清单 3. 接收会议类型
<field name="TypeOfMeeting">
<prompt>Say the type of appointment. Options are meeting, dentist, 
                                            doctor, party. </prompt>
      <grammar type="text/gsl">
        <![CDATA[[
          [meeting] {<TypeOfMeeting "meeting">}
          [dentist] {<TypeOfMeeting "dentist">}
          [doctor] {<TypeOfMeeting "doctor">}
          [party] {<TypeOfMeeting "party">}
        ]]]>
      </grammar>
</field>

注意,清单 3 中的代码指定了信息目的地的目标字段,以及将写入该字段的最终数据。

用户提供了需要填入字段的信息之后,程序将调用 VoiceXML 中的填充块。对于应用程序,我们将使用它重新指定约会的日期、时间和类型。

当用户说出特定的数据类型时,该信息会将要转换为语音的信息类型告诉语音浏览器的 TTS 系统。比如说,如果我们想使用典型的日期 “17/5/2007”,那么将其读出来可能是 “seventeen-slash-five-slash-two thousand and seven”。这个字符串本身是没有任何意义的。但是,如果 TTS 系统将其识别为日期,那么取而代之可以将该字符串读作 “seventeenth of May two thousand and seven”。

say-as 标记可以通知 TTS 解析器如何读出项目,interpret-as 标记将指示数据类型,而 format 标记将指示信息的规则。比如说 “dmy” 将表示日期的格式为日期-月-年。

我们可以使用相同的标记读出时间(比如说,这样可以将 “11:30” 识别为 “half past eleven”,而不是纯粹的数字)。我们可以使用 VXML 生成这种说明,如清单 4 所示。

清单 4. 再次确认输出
        <filled>
          <prompt>
        You have specified a date of:
        <say-as interpret-as="date" format="dmy">
          <value expr="Day"/>/<value 
                  expr="Month"/>/<value expr="Year"/>
        </say-as>
        At:
        <say-as interpret-as="time" format="hm">
          <value expr="Hour"/>:<value expr="Minute"/>
        </say-as>
              <break/>
        </prompt>
        <prompt>Appointment type of 
          <value expr="TypeOfMeeting"/>
        </prompt>

最后,接收到所有信息之后,我们需要将用户输入发送脚本进行处理。然后,脚本将把信息保存在 XML 日程表文件中,该文件存储了所有的约会信息。

VXML 中 的 submit 标记可用于在 VXML 和能够处理信息的脚本之间交换字段信息。应该将要提供的字段用空格隔开放在 namelist 属性中。

然后,语音浏览器会将这些信息转换为标准的 http 字段/值字符串,这样我们便可以解析和提取这些信息,其方法与标准 HTML 页面中通过表单将参数提供给脚本的方法相同。清单 5 中的 VXML 代码将从变量中将响应发送给用于存储约会的脚本。

清单 5. 将输入字段数据提交给外部脚本
        <submit 
         next="savedate.cgi" 
         namelist="Day Month Year Hour Minute TypeOfMeeting"
         method="post"/>
        </filled>

        </form>
        
</vxml>

我们可以改进 VoiceXML 应用程序的交互功能。简单的说,例如要改进接收输入的方法,我们可以编写一个月语法规则,这样应用程序就可以直接识别月(如清单 6 所示)。

清单 6. 月的语法规则
<grammar type="text/gsl">
  <![CDATA[[
    [january] {<TypeOfMeeting "1">}
    [february] {<TypeOfMeeting "2">}
    [march] {<TypeOfMeeting "3">}
...
    [december] {<TypeOfMeeting "12">}
  ]]]>
</grammar>

还有一些其他方面的改进,即当用户选择提交信息时应用程序会重新开始输入流程。最好通过脚本来处理信息确认方面的问题,这样便可以确认输入值类型是否正确并检查它们是否已经存在。这些任务和保存约会都是脚本的一部分。

保存条目

约会数据将保存在 XML 文件中,并且我们将通过一个 VoiceXML 表单读取它们,该表单会将这些数据作为 HTTP 参数提交。示例脚本将使用 Perl 读取该信息,并使用 XML::DOM 模块加载已有的 XML 。然后,添加新约会并将其写入更新文件,但是我们也可以轻易地使用 Java™、Python 或 PHP 脚本来处理输入。

XML 文件的格式如清单 7 所示。

清单 7. 使用 Perl 脚本读取信息
<diary>
<meeting date="26/3/2007" time="12:30" type="Party"/>
</diary>

会议标记包含一切所需的内容,实际数据存储于它的各个属性中。这样可以简化信息的写入和更新。要确保能够接收到有效的数据,并且避免将错误信息写入 XML,我们可以访问其参数并相应地设置 ok 变量。

在这里,我们也可以使用所提供的数据创建一个 DateTime 对象,用于确认所提供数日期的有效性,如果数据无效则将 ok 变量设置为 false。ok 变量的值为 false 将触发脚本输出一个 VXML 代码段,用于将用户重定向到 addentry.vxml 文件,该文件将要求用户重新指定其约会信息。如果信息有效,那么我们将更新 XML 日程文件并取消将用户重定向到主菜单的 VXML 代码段。

清单 8 显示了完整的脚本。

清单 8. 将约会保存在 XML 文件中
#!/usr/bin/perl

use CGI qw/:standard/;
use XML::DOM;

print header(-type => 'text/xml');

my $ok = 1;
foreach my $param (qw/Day Month Year Hour Minute TypeOfMeeting/)
{
    $ok = 0 if (!defined(param($param)));
}

if ($ok)
{
    my $parser = new XML::DOM::Parser;
    my $doc = $parser->parsefile ("dates.xml");
    
    my $meeting = $doc->createElement('meeting');
    $meeting->setAttribute('date', sprintf('%s/%s/%s',
                                           param('Day'),
                                           param('Month'),
                                           param('Year')));
    
    $meeting->setAttribute('time', sprintf('%s:%s',
                                           param('Hour'),
                                           param('Minute')));
    
    $meeting->setAttribute('type', param('TypeOfMeeting'));
    
    my $diary = $doc->getElementsByTagName ("diary")->item(0);
    $diary->appendChild($meeting);
    
    open(DATA,">dates.xml");
    print DATA $doc->toString;
    close(DATA);
print <<EOF;
<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
<form>
<block>
<prompt>Appointment saved.<break time="2000"/></prompt>
<goto next="calmenu.vxml"/>
</block>
</form>
</vxml>
EOF

}
else
{
    print <<EOF;
<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
<form>
<block>
<prompt>Sorry, there was a problem with your appointment. 
Please try again.<break time="2000"/></prompt>
<goto next="entry.vxml"/>
</block>
</form>
</vxml>
EOF
}

我们还需要最后一个脚本 — 该脚本将读取已有日程 XML 文件并读出已有约会。

输出当前内容

日程脚本将读取 diary.xml 文件并生成一个 VXML 代码段,该代码段将读出当前日程的内容。实现方法相当简单直观,最后将生成 VXML 输出。日程列表的末尾有一个返回选项,可以使用户返回主菜单 VXML 文件。完整脚本如清单 9 所示。

清单 9. 通过日程文件输出 VXML
#!/usr/bin/perl

use CGI qw/:standard/;
use XML::DOM;

print header(-type => 'text/xml');

my $parser = new XML::DOM::Parser;
my $doc = $parser->parsefile ("dates.xml");

my $nodes = $doc->getElementsByTagName ("meeting");
my $n = $nodes->getLength;

print <<EOF;
<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <form>
<block><prompt>Your current diary.<break 
                         time="2000"/></prompt></block>
EOF

for (my $i = 0; $i < $n; $i++)
{
    my $node = $nodes->item ($i);
    my $daten = $node->getAttributeNode ("date");
    my $timen = $node->getAttributeNode ("time");
    my $typen = $node->getAttributeNode ("type");
    
    my $date = $daten->getValue;
    my $time = $timen->getValue;
    my $type = $typen->getValue;

    print <<EOF;
    <block>
        <prompt>
        <say-as interpret-as="date" format="dmy">$date</say-as>,
      at 
        <say-as interpret-as="time" format="hm">$time</say-as>,
        <break time="500"/>      
      $type
        <break time="1000"/>
        </prompt>
EOF

if ($i == ($n-1))
{
    print '<prompt><break time="1000"/>End of diary. 
Returning to main menu.<break time="2000"/></prompt><goto 
next="calmenu.vxml"/>';
}

print '</block>';
}

print <<EOF;
  </form>
</vxml>
EOF

清单 10 显示了输出的示例。

清单 10. 日程输出的示例 VXML 代码
Content-Type: text/xml; charset=ISO-8859-1

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <form>
  <block>
    <prompt>Your current diary.<break time="2000"/></prompt>
  </block>
  <block>
    <prompt>
      <say-as interpret-as="date" format="dmy">26/3/2007</say-as>,
        at 
      <say-as interpret-as="time" format="hm">12:30</say-as>,
      <break time="500"/>      
        Party
      <break time="1000"/>
    </prompt>
  </block>
  <block>
    <prompt>
      <say-as interpret-as="date" format="dmy">1/3/2007</say-as>,
        at 
      <say-as interpret-as="time" format="hm">12:30</say-as>,
      <break time="500"/>      
        doctor
      <break time="1000"/>
    </prompt>
  </block>    
  <block>
    <prompt>
      <say-as interpret-as="date" format="dmy">2/2/2007</say-as>,
        at 
      <say-as interpret-as="time" format="hm">10:30</say-as>,
      <break time="500"/>      
        party
      <break time="1000"/>
    </prompt>
    <prompt>
      <break time="1000"/>
        End of diary. Returning to main menu.
      <break time="2000"/>
    </prompt>
    <goto next="calmenu.vxml"/>
  </block>  
</form>
</vxml>

您可以在更新版本的应用程序中对当前脚本中的众多元素进行改进和扩展。您可以提供一个额外的菜单,让用户可以选择输出的持续时间。比如说,您可以让用户说出以下短语(通过一个额外的 VoiceXML 菜单):

  • 今天(Today) — 当天的约会。由于输出和选择将以脚本为依据,因此我们可以确认当天的约会并动态的筛选 XML。
  • 明天(Tomorrow)— 第二天的约会。
  • 具体日期(Specific date)。

使用上文所介绍的规则,这一过程的基本原理将十分清楚。

结束语

在本文中,我们了解了如何扩展 VoiceXML 应用程序与支持它的脚本之间的交互性。此处的关键因素是 submit 标记,它使我们能够将信息提交给脚本,并且其方式与将字段提交给任何普通 Web 脚本的方式一样。这种信息交互方式使在应用程序、已有数据和基于语音的浏览器或接口之间进行交互成为了可能。

务必请阅读下一部分,即本系列的第 3 部分。第 3 部分将介绍如何开发一个简单的博客发布应用程序,该应用程序将使用 VoiceXML 作为输入并将数据保存到在线博客中。您还将了解如何在 VoiceXML 中使用 “tweets”,也就是 Twitter 条目。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=263751
ArticleTitle=支持语音的 XML,第 2 部分: 开发支持语音的日程表
publish-date=10222007