内容


支持语音的 XML,第 1 部分

开发支持语音的 RSS 阅读器

Comments

系列内容:

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

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

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

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

简介

希望使用支持语音的 RSS 阅读器的任何人都会从本文受益。您还将了解 VoiceXML 的基础知识和 RSS XML 格式,还包括:

  • 使用 XSLT 将 RSS 转换为 VXML
  • 编写一个 Perl 脚本来生成 VXML
  • 向 VXML 文件增加交互性
  • 使用 Java™ servlet 生成 VXML

关于本系列

语音(和一般的音频)在网上越来越流行了。这方面的示例包括大量的网络音乐和网络广播。本系列讲解几种结合使用语音和 XML 开发应用程序的方式:

VoiceXML 基础

VoiceXML 一名源于一种标准,用于基于语音的 XML 输出,但是这种文件格式本身称为 VXML。

如果使用合适的 VoiceXML 浏览器,那么 VXML 可以实现非常强大的功能;VoiceXML 浏览器可以将 VoiceXML 内容转换为语音 [即文本到语音(Text-To-Speech,TTS)],也可以识别语音命令(语音识别)。

清单 1 显示 VXML 文件的基本格式。

清单 1. VXML 文件的基本格式
<?xml version="1.0"?>
<vxml xmlns="http://www.w3.org/2001/vxml" version="2.0">
...
</vxml>

然后嵌入表单元素来提供对信息的选择,并将它放在块中。TTS 嵌入在 <prompt> 标记中。因此,可以使用清单 2 中的 VXML 产生一个简单的语音句子。

清单 2. VXML 文件示例
<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <form>
    <block>
      <prompt>
        I could ask you anything!
      </prompt>
    </block>
  </form>
</vxml>

这个提示需要响应提供有效单词的列表,语音识别系统可以接收并识别这些单词;也可以通过电话键盘进行输入。然后,将响应的实际信息存储在变量中,可以使用简单的 if/else 条件语句结构输出不同的响应。

在本文和本系列中,也将使用这样的方法。

要想使用本文中的示例,需要能够访问一个 Web 托管服务(可以在那里提供 VXML 文件)和一个 VoiceXML 浏览器(提供 TTS 和 VR 组件),通常通过某种形式的电话线。Voxeo 可以提供所需的语音浏览器服务,可以通过固定电话(land line)接入您的应用程序(使用一个特殊的 pin 码),还提供一个基于 Session Initiation Protocol(SIP)的 VoIP 服务,甚至可以使用 Skype。更多信息参见 参考资料

了解 RSS 源格式

Really Simple Syndication(RSS)是一种基于 XML 的信息发布解决方案,常常用在 blog 和其他站点上。RSS 格式让我们能够生成文章或信息的列表,还可以组合许多 RSS 提要的来源(聚合)。产生的结果是一个很容易格式化的文章列表,包括完整文章的 URL、摘要信息和每个条目的分类信息。完整的 RSS 提要也包含分类以及进一步的细节和分类数据。

RSS 只是众多的 XML 联合格式之一。在 RSS 文件中,基本结构非常简单。为了了解 RSS 的结构,先看看清单 3 所示的简化结构。

清单 3. RSS 结构示例
<?xml version="1.0" encoding="UTF-8"?>
<!-- generator="wordpress/2.0.4" -->
<rss version="2.0" 
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  >

<channel>
  <title>MCslp</title>
  <link>http://mcslp.com</link>
  <description>News from the desk of Martin MC Brown</description>
  <pubDate>Thu, 19 Apr 2007 08:14:30 +0000</pubDate>
  <generator>http://wordpress.org/?v=2.0.4</generator>
  <language>en</language>
    <item>
      <title>IBM developerWorks Podcast Interview</title>
      ...
    </item>
    <item>
      <title>...</title>
      ...
    </item>
</channel>
</rss>

开头的内容从整体上描述提要中的信息 —— 在这个示例中,我将 blog 标题设置为 MCslp 并提供描述 “News from the desk of Martin MC Brown”。

清单 3 的其余部分为提要提供额外信息,比如发布日期(对于从 blog 生成的动态提要,就是生成 RSS XML 的时间),然后是针对每个条目的块信息。

每个条目块有一个简单的标题和一组摘要信息,如果 RSS 提要支持的话,还可以包含一个更详细的信息(有时候是全文)。清单 4 给出每个条目的格式。

清单 4. 用 XML 编写的 RSS 条目
<item>
  <title>IBM developerWorks Podcast Interview</title>
  <link>http://mcslp.com/?p=250</link>
  <comments>http://mcslp.com/?p=250#comments</comments>
  <pubDate>Thu, 19 Apr 2007 08:14:28 +0000</pubDate>
  <dc:creator>Martin MC Brown</dc:creator>

  <category>Articles</category>
  <category>Interviews</category>
  <category>IBM developerWorks</category>
  <category>Grids</category>
  <guid isPermaLink="false">http://mcslp.com/?p=250</guid>
  <description>
    <![CDATA[
      Summary
    ]]>
  </description>
  <content:encoded>
    <![CDATA[
      Full information
    ]]>
  </content:encoded>
</item>

对于信息的 VoiceXML 版本,可以忽略其中一些元素。在读取信息的语音版本时,有些元素是没有意义的 —— 例如类别就可能没有意义,guid 和其他元素实际上只供内部使用。

在信息中,真正有用的部分是整个提要的标题、每个条目的标题和每个条目的描述或内容。

我们来讨论一种将 RSS 文件转换为 VoiceXML 的非常简单的方法。

使用简单的 XSL 转换

对于将现有的 RSS XML 文件转换为 VXML,最快速、最简单的方法之一是使用 XSL 模板将 RSS XML 转换为所需的输出。这个过程有一些限制,导致某些操作非常困难,这主要是因为有时候在 XSL 中选择元素和做出决策的方法非常复杂,但是 XSL 是一种不错的快速解决方案。

清单 5 给出一个简单的 XSL 样式表,它将 RSS 源文件格式标题和描述元素转换为 VXML,从而读出 RSS 提要中每个条目的标题和内容。

清单 5. 将 RSS 转换为 VXML 的 XSL 样式表
<?xml version="1.0" encoding ="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" omit-xml-declaration="no"/>
    <xsl:template match="/">

        <vxml version="2.0"
            xmlns="http://www.w3.org/2001/vxml">
            <form id="news">
                <xsl:for-each select="rss/channel/item">

                    <block>
                        <prompt>
                            <xsl:value-of select="title" />
                            <break size="small" />
                            <xsl:value-of select="description" />

                            <break size="medium" />
                        </prompt>
                    </block>

                </xsl:for-each>
            </form>
        </vxml>
    </xsl:template>
</xsl:stylesheet>

这个 XSL 样式表使用一个 XPath 定义选择并处理 RSS 中的条目。然后将标题和描述的值插入一个提示块,并用 break 块将它们分隔开。在 VoiceXML 中,break 标记会在 TTS 输出中产生一个停顿。

可以使用 xsltproc 对一个 RSS XML 文档运行这个样式表(在下面的示例中,文档来自 BBC 新闻 Web 站点),见清单 6。

清单 6. 用 XSL 样式表生成 VXML
$ xsltproc rsstovxml.xsl rss.xml
<?xml version="1.0"?>
<vxml xmlns="http://www.w3.org/2001/vxml" version="2.0">
<form id="news">
  <block>
    <prompt>Prince Harry not to serve in Iraq
      <break size="small"/>
        Prince Harry will not be deployed in Iraq because of 
        the security threat, the head of the Army says.
      <break size="medium"/>
    </prompt>
  </block>
...
</form>
</vxml>

可以看到,对于提要中的每个条目,输出一个块、标题和条目的摘要内容。可以将生成的 VXML 上载到浏览器中并听到语音输出。这个结果不太实用,因为需要增加一个下载解决方案并定期自动地生成文件。所以,需要考虑如何更动态地产生所需的信息。

将 RSS 转换为 VoiceXML 工作流

XSL 样式表方法的一个问题是,虽然它非常简单,但是功能非常有限,在生成 VXML 文件时可用的选项和生成的 VXML 两方面都受限制。为了克服这些限制,可以使用 Perl 或 Java 编程语言执行转换,这会给生成过程增加一点儿复杂性。

在此之前,我们讨论一下 RSS 浏览器的工作方式。在图 1 中可以看到这个过程的示例。

图 1. 将 RSS 转换为 VXML 应用程序的工作流
将 RSS 转换为 VXML 应用程序的工作流
将 RSS 转换为 VXML 应用程序的工作流

当调用一个编号时,向调用者提供可用 RSS 提要的列表。当用户选择一个提要时,处理这个提要的内容并列出这个提要中每个条目的标题。然后,调用者可以选择要听的新闻,就可以听到更详细的摘要。

首先要处理提要并输出 VXML,从而生成信息的主列表。

用 Perl 从 RSS 生成 VoiceXML

有许多 Perl 模块可以处理联合提要,但是到目前为止最实用的是 XML::FeedPP 模块。这个模块可以处理任何联合提要,包括 RSS、RDF 和 Atom 格式,并可以通过简化的界面提供内容。

为了解析提要,应该在创建新的 XML::FeedPP 对象时提供提要的 URL:my $feed = XML::FeedPP->new($feedurl);

可以从这个顶级对象访问这个提要的标题和其他信息。例如,可以使用 $feed->title(); 获得提要的标题。

为了获得提要中的各个条目,可以使用 get_item() 方法获得条目对象的列表,然后使用其他方法(比如 title()description())获得每个条目的实际内容。

因此,可以使用清单 7 中的代码从给定的提要产生新闻条目的列表。

清单 7. 用 Perl 从 RSS 提要生成 VXML 文件
use XML::FeedPP;

my $feedurl 
   = 'http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/uk/rss.xml';

my $feed = XML::FeedPP->new($feedurl);

my ($selection) = ('');

$selection = '<form><block>';
$selection .= '<prompt>' . $feed->title() . 
              '<break size="small"/></prompt>';

foreach my $i ( $feed->get_item() )
{
    next unless defined($i);
    next unless ($i->link() =~ m/http/);

    $selection 
       .= sprintf('<prompt>%s.<break size="small"/></prompt>',
                  $i->title());
}

$selection .= '</block></form>';

print <<EOF;
<?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">
$selection
</vxml>
EOF

清单 7 中的脚本有两个主要部分。第一个部分获得提要并为每个新闻条目标题输出提示块;这些输出依次追加到一个字符串中。第二个部分将整个字符串嵌入 VXML 文件的核心。

对条目的列表进行循环遍历,对于每个条目输出标题,然后是一个短暂的音频停顿。可以使用成熟的 XML 生成器输出这些信息。尽管生成 XML 是相当简单的,但是可以只用标准的 print 语句生成信息。

还要注意,这个 RSS 提要是硬编码的,而且生成的输出发送到标准输出,所以如果希望将输出写到文件中,就需要将输出重定向到文件。产生的 VXML 见清单 8(为了节省空间,做了删减)。

清单 8. 从 RSS 提要自动生成的 VXML
<vxml version="2.1" xmlns="http://www/w3/org/2001/vxml"
  xml:lang="en-US">

  <form>
    <block>
      <prompt>
        BBC News | UK | UK Edition
        <break size="small" />
      </prompt>
      <prompt>
        Prince Harry not to serve in Iraq.
        <break size="small" />
      </prompt>
      <prompt>
        Madeleine fighting fund launched.
        <break size="small" />
      </prompt>
...
    </block>
  </form>

</vxml>

如果将这个文件上载到兼容的 VoiceXML 服务并接入,就能够听到来自这个提要的新闻列表。

在这个示例中,仍然只能获得标题。更好的实现是收听标题,然后选择要收听的新闻条目的摘要。

增加新闻选择功能和更多细节

为了增加选项和摘要特性,在生成 VXML 时需要做几件事:

  1. 需要创建选项的列表(在选项之前或之后加一个编号),还要提供接收所选编号的功能。
  2. 需要能够解析输入的编号。可以只通过语音进行解析,但是这个示例接受 DTMF 输入。
  3. 需要提供全文输出。

这些任务中的第一项非常容易 —— 只需要产生一个惟一的编号,并与新闻标题一起输出这个编号。在选择一个编号时,为了确保新闻标题(及其编号)与完整的摘要匹配,要使用两个临时的字符串。一个字符串包含可能的选择,另一个字符串包含详细的输出。

为了从键盘捕捉编号,需要在 VXML 中指定一个捕捉输入的字段:<field name="select_num" type="digits">

现在,系统将等待用户输入某个数字。填充变量之后,解析 VXML 中填充的块。这用来选择要输出的新闻摘要信息,然后输出适当的要进行朗读的文本块。

为了解析这个值,要从 Dual Tone Multi-Frequency(DTMF)信号中捕捉输入的数字,然后将这个数字放到一个变量中:<assign name="selection" expr="select_num"/>

按照这种方式,可以接收多个数字,但是这个示例只输出前 6 个新闻,所以不需要接收多个数字。

捕捉到输入的数字之后,可以使用 if 块选择要播放的新闻。

在清单 9 中可以看到完整的 Perl 脚本。

清单 9. 完整的 Perl 脚本
use XML::FeedPP;

my $feedurl 
   = 'http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/uk/rss.xml';

my $feed = XML::FeedPP->new($feedurl);

my ($selection,$detail,$counter) = ('','',0);

$selection = '<form id="MainMenu"><field name="select_num" type="digits">';
$selection .= '<prompt>' . $feed->title() . 
                          '<break size="small"/></prompt>';
$selection .= '<prompt>Please select a story from the 
                           following list.</prompt>';

foreach my $i ( $feed->get_item() )
{
    next unless defined($i);
    next unless ($i->link() =~ m/http/);

# We only list the top six stories

    last if ($counter++ >= 6);

    if ($counter == 1)
    {
        $detail .= '<filled><assign name="selection" expr="select_num"/>';
        $detail .= "<if cond=\"selection =='$counter'\">";
    }
    else
    {
        $detail .= "<elseif cond=\"selection =='$counter'\"/>";
    }
    $detail .= sprintf('<prompt>%s. %s<break size="small"/>
<reprompt/></prompt>',$i->title(),$i->description());

    $selection .= sprintf('<prompt>%d: %s<break 
size="small"/></prompt>',$counter,$i->title());
}

$selection .= '<noinput>Please select a number. 
                              <reprompt/></noinput>';
$selection .= '<nomatch>Please select a valid number. 
                              <reprompt/></nomatch>';
$selection .= '</field>';

$detail .= '</if><clear 
namelist="select_num"/><reprompt/></filled></form>';

print <<EOF;
<vxml version="2.1">
$selection
$detail
</vxml>
EOF

生成的 VXML 见清单 10。在输出中可以看到如何列出新闻,如何提供输入的机会,如何帮助收听者选择接下来的操作。

清单 10. 通过 VXML 提供新闻选择功能
<vxml version="2.1">
  <form id="MainMenu">
    <field name="select_num" type="digits">
      <prompt>
        BBC News | UK | UK Edition
        <break size="small" />
      </prompt>
      <prompt>
        Please select a story from the following list.
      </prompt>
      <prompt>
        1: Prince Harry not to serve in Iraq
        <break size="small" />
      </prompt>
      <prompt>
        2: Madeleine fighting fund launched
        <break size="small" />
      </prompt>
      <prompt>
        3: Salmond elected as first minister
        <break size="small" />
      </prompt>
      <prompt>
        4: Sainsbury profits jump to £380m
        <break size="small" />
      </prompt>
      <prompt>
        5: Police boo John Reid over pay
        <break size="small" />
      </prompt>
      <prompt>
        6: Uncle jailed for owning death dog
        <break size="small" />
      </prompt>
      <noinput>
        Please select a number.
        <reprompt />
      </noinput>
      <nomatch>
        Please select a valid number.
        <reprompt />
      </nomatch>
    </field>
    <filled>
      <assign name="selection" expr="select_num" />
      <if cond="selection =='1'">
        <prompt>
          Prince Harry not to serve in Iraq. Prince Harry will
          not be deployed in Iraq because of the security
          threat, the head of the Army says.
          <break size="small" />
        </prompt>
        <elseif cond="selection =='2'" />
        <prompt>
          Madeleine fighting fund launched. A fighting fund is
          launched to help cover escalating costs in the
          search for missing Madeleine McCann.
          <break size="small" />
        </prompt>
        <elseif cond="selection =='3'" />
        <prompt>
          Salmond elected as first minister. Alex Salmond
          makes history after becoming the first Nationalist
          to be elected first minister of Scotland.
          <break size="small" />
        </prompt>
        <elseif cond="selection =='4'" />
        <prompt>
          Sainsbury profits jump to £380m. Sainsbury's, the
          supermarket chain that was the target of takeover
          speculation, sees its full-year profits surge.
          <break size="small" />
        </prompt>
        <elseif cond="selection =='5'" />
        <prompt>
          Police boo John Reid over pay. Home Secretary John
          Reid is booed over pay proposals at the Police
          Federation's annual conference.
          <break size="small" />
        </prompt>
        <elseif cond="selection =='6'" />
        <prompt>
          Uncle jailed for owning death dog. The uncle of a
          five-year-old girl killed by a pit bull terrier is
          jailed for eight weeks for owning an illegal dog.
          <break size="small" />
        </prompt>
      </if>
      <clear namelist="select_num" />
      <reprompt />
    </filled>
  </form>
</vxml>

如果将这个文件上载到 VoiceXML 浏览器,就可以听到新闻列表并选择要收听的新闻。新闻列表后的最后一块提供选择另一个条目的机会(见清单 11)。

清单 11. 选择另一个条目
<clear namelist="select_num" />
<reprompt />

必须清空这个变量,否则这个变量仍然包含上一次的输入值,进一步的选择就是无效的。

对于前面的选择,动态地下载 RSS,但是仍然输出静态文件。为了提供真正的交互性,需要动态地生成这些信息。

使用 Java servlet 创建 RSS VoiceXML

到目前为止,选择都基于静态 VXML 文件中提供的信息。对于更具交互性的元素,需要通过脚本动态地生成信息。大多数语音浏览器服务通过读取 URL 来访问 VXML 文件,所以需要开发一个 CGI 或其他动态 Web 脚本来创建这些信息。

可以用前面两个 Perl 示例(清单 7清单 9)动态地创建信息,这需要确保在生成 VXML 之前输出正确的 HTTP 头(text/xml)。为此,只需要在脚本顶部增加两个元素(见清单 12)。

清单 12. 在脚本顶部增加两个元素
use CGI qw/:standard/;
print header(-type => 'text/xml');

这些代码导入 CGI 库,然后发送正确的 HTTP 报头信息;输出的其余部分是与以前相同的 VXML。

同样的原理也适用于其他语言。清单 13 给出一个 Java servlet,它提供与第一个 Perl 示例(清单 7)相同的输出。这个 servlet 使用 Rome 和 JDOM 库解析 RSS 提要,然后输出所需的 VXML。

清单 13. 通过 VXML 和 JSP 生成 RSS 新闻提要
import java.net.URL;
import java.util.Iterator;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import javax.servlet.*;
import javax.servlet.http.*;
import com.sun.syndication.feed.module.Module;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;

public class VXMLRSS extends HttpServlet {

    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        BufferedInputStream bis = null;
        PrintWriter out = null;

        try {
            out = res.getWriter();
            res.setContentType("text/xml");

            printHeader(out);
            printNews(out);
            printFooter(out);

        } finally {
            if (out != null) out.close();
            if (bis != null) bis.close();
        }
    }

    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        doGet(req, res);
    }

    private void printHeader(PrintWriter out) throws IOException {
        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        out.println("<vxml version=\"2.1\">");
    }

    private void printNews(PrintWriter out) throws IOException {
        try {
            final URL feedUrl = new 
URL("http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/uk/rss.xml");

            final SyndFeedInput input = new SyndFeedInput();
            final SyndFeed feed = input.build(new XmlReader(feedUrl));

            out.println("<form><block>" + 
                        "<prompt>" + 
                        feed.getTitle() + 
                        "<break size=\"small\"/></prompt>");

            for (final Iterator iter = feed.getEntries().iterator();
                 iter.hasNext();)
                {
                    out.println("<prompt>" +
                                ((SyndEntry)iter.next()).getTitle() + 
                                "<break size=\"small\"/></prompt>");
                }
            out.println("</block></form>");
        }

        catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("ERROR: " + ex.getMessage());
        }
    }

    private void printFooter(PrintWriter out) throws IOException {
        out.println("</vxml>");
    }
}

这个提要解析器的基本操作和 VXML 的生成过程与 Perl 示例相同。差异只在于动态地获取提要并生成输出,而不是创建静态的 VXML 文件。

可以将这个程序(以及必需的 JDOM 和 Rome JAR 文件)安装到 JSP 服务器环境中,比如 Apache Tomcat,然后以此作为 VXML 应用程序的基础。

动态地生成不同的 VXML

这个过程的最后一部分是提供新闻提要的列表,让调用者可以选择要收听的新闻,这也提供了提高交互性的机会:在选择某个条目时,调用另一个脚本或者访问另一个位置。

首先提供可用 RSS 提要的列表。在这个示例中使用一个数组,但是也可以从数据库装载这些信息。解析每个提要来获得提要标题,然后像以前一样输出可用提要列表,并等待输入数字。但是,在选择一个编号时,现在要调用一个外部 URL(在这个示例中实际上是调用同一个脚本),并指定新闻提要的编号作为参数。然后调用一个函数,输出这个新闻提要中的条目并让用户选择新闻条目。

清单 14 给出完整的 Perl CGI 脚本。

清单 14. CGI 脚本
#!/usr/bin/perl

use XML::FeedPP;

use CGI qw/:standard/;

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

my ($feeds) = ['http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/uk/rss.xml',
               'http://mcslp.com/wp-rss2.php'];


if (param('selection') =~ m/[0-9]+/)
{
    output_news_feed($feeds->[param('selection')]);
}
else
{
    output_feed_list();
}

sub output_feed_list
{
    my ($selection,$detail,$counter) = ('','',0);

    $selection = '<form id="MainMenu"><field name="select_num" 
                                                           type="digits">';
    $selection .= '<prompt>MCSLP News Feed 
                             Reader<break size="small"/></prompt>';
    $selection .= '<prompt>Please select a news source from the following 
                                                              list.</prompt>';

    foreach my $feedurl (@{$feeds})
    {
        my $feed = XML::FeedPP->new($feedurl);

        $counter++;

        if ($counter == 1)
        {
            $detail .= '<filled><assign name="selection" 
                                                       expr="select_num"/>';
            $detail .= "<if cond=\"selection =='$counter'\">";
        }
        else
        {
            $detail .= "<elseif cond=\"selection =='$counter'\"/>";
        }
        $detail .= sprintf('<goto next="http://www.mcslp.com/
                                             rsstovxmlopt.cgi?selection=%s"/>',
                           $counter);

        $selection .= sprintf('<prompt>%d: %s<break 
size="small"/></prompt>',$counter,$feed->title());
    }

    $selection .= '<noinput>Please select a number. 
                                          <reprompt/></noinput>';
    $selection .= '<nomatch>Please select a valid number. 
                                          <reprompt/></nomatch>';
    $selection .= '</field>';

    $detail .= '</if><clear 
namelist="select_num"/><reprompt/></filled></form>';

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

<vxml version="2.1">
$selection
$detail
</vxml>
EOF

}

sub output_news_feed
{
    my ($feedurl) = @_;

    my $feed = XML::FeedPP->new($feedurl);

    my ($selection,$detail,$counter) = ('','',0);

    $selection = '<form id="MainMenu"><field name="select_num" 
                                                              type="digits">';
    $selection .= '<prompt>' . $feed->title() . 
                                     '<break size="small"/></prompt>';
    $selection .= '<prompt>Please select a story from the following 
                                                            list.</prompt>';

    foreach my $i ( $feed->get_item() )
    {
        next unless defined($i);
        next unless ($i->link() =~ m/http/);

        last if ($counter++ >= 6);

        if ($counter == 1)
        {
            $detail .= '<filled><assign name="selection" 
                                                          expr="select_num"/>';
            $detail .= "<if cond=\"selection =='$counter'\">";
        }
        else
        {
            $detail .= "<elseif cond=\"selection =='$counter'\"/>";
        }
        $detail .= sprintf('<prompt>%s. %s<break 
size="small"/></prompt>',$i->title(),$i->description());
        
        $selection .= sprintf('<prompt>%d: %s<break 
size="small"/></prompt>',$counter,$i->title());
    }

    $selection .= '<noinput>Please select a number. 
                                           <reprompt/></noinput>';
    $selection .= '<nomatch>Please select a valid number. 
                                           <reprompt/></nomatch>';
    $selection .= '</field>';

    $detail .= '</if><clear 
namelist="select_num"/><reprompt/></filled></form>';

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

<vxml version="2.1">
$selection
$detail
</vxml>
EOF
}

这个脚本之所以可以工作是因为,用来提供提要列表的提要编号与用来选择 提要本身的提要编号是相同的。对于数据库解决方案,可能希望给每个新闻提要分配一个惟一的 ID,并用这个 ID 进行选择,但是原理是相同的。

顺便说一句,可以使用 VXML 产生所需的所有输出。VXML 支持在一个文件中提供不同的表单块,可以为所有解决方案产生输出。但是,动态地生成如此大量的 VXML 可能不太现实,所以用脚本动态生成的信息量一般比较少。

对于动态解决方案,必须防止信息的生成减慢整个处理过程。例如,在前面的脚本中,访问了主列表中的每个提要,这可能非常耗费时间。通过使用数据库驱动的解决方案,可以更快地获得提要的标题并输出信息。

还要注意,在使用动态解决方案时,可能需要更多的错误处理。在这个示例中,如果读取新闻提要失败,那么会产生空的新闻列表。如果无法正确地处理 提要,那么最好增加一个简单的提示 “Sorry this service is unavailable”,这可以帮助用户应付这种失败。

结束语

在本文中,构建了许多不同的解决方案,从而生成 RSS 提要语音功能所需的 VXML。首先研究了简单的 XSL 转换,然后讨论了更高级的基于 Perl 和 Java 的解决方案。

这些脚本本身相当简单;真正具有强大功能的是它们生成的 VXML,以及使用 VXML 提供基于语音的界面的语音浏览器。可以结合使用 VXML 和动态脚本提供的交互性,相当轻松地建立复杂的语音应用程序。

请期待第 2 部分,我们将开发一个支持语音的日历。


下载资源


相关主题


评论

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=254388
ArticleTitle=支持语音的 XML,第 1 部分: 开发支持语音的 RSS 阅读器
publish-date=09102007