Голосовая поддержка в XML: Часть 1. Разрабатываем программу прослушивания RSS-каналов (исходники)

Мартин Браун

Введение

Эту статью будет полезно прочитать всем интересующимся преимуществами использования голосовых программ для чтения RSS-лент. К тому же вы познакомитесь с основами VoiceXML и форматом RSS XML. Но главное, вы научитесь:

  • Трансформировать RSS в VXML, используя XSLT
  • Писать скрипты на Perl для генерации VXML
  • Добавлять поддержку интерактивности в ваши файлы VXML
  • Генерировать файлы VXML, используя сервлеты Java™

Об этой серии

Аудио и, в частности, голосовые сервисы становятся все более популярны в Интернет. В качестве примеров могут служить всевозможные музыкальные ресурсы, а также Web-трансляции, доступные в онлайн. Статьи нашей серии рассказывают о способах совмещения голосовых технологий и XML при разработке таких приложений, как:

  • В первой части: RSS-reader с голосовой поддержкой
  • Во второй части: календаря с голосовой поддержкой
  • В третьей части: голосовой программы для работы с блогами и Twitter
  • В четвертой части: программы для голосового поиска в Yahoo

Основы VoiceXML

VoiceXML - это общее название для голосовых данных XML, в то время как сам файловый формат называется VXML.

VXML особенно полезен при использовании совместно с браузером 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>

Если в ответ на голосовые запросы ожидается какое-либо действие пользователя, то в элементах <prompt> можно либо перечислить список ответов, корректно воспринимаемых системой распознавания речи, либо же запросить ввод с телефонной клавиатуры. Полученная информация затем сохраняется в переменных, так что можно использовать обычные условные операторы (if/else) для формирования ответов.

Все эти методы будут продемонстрированы как в данной статьей, так и в последующих статьях серии.

Для запуска примеров к статье вам понадобится доступ к хостингу для размещения файлов VXML, а также браузер VoiceXML с функциями TTS и распознавания речи. Как правило, подобные компоненты доступны через различного рода телефонные линии. Например, Voxeo предоставляет голосовой браузер, а также все необходимые сервисы, в частности, возможность дозваниваться до ваших приложений со стационарных телефонов, используя специальный пин-код. Это осуществляется через специальный VoIP-сервис на основе протокола SIP (Session Initiation Protocol) или же просто через Skype.

Формат файлов RSS

RSS (Really Simple Syndication) - это основанная на XML технология, предназначенная для публикации информации, часто используемой в блогах и подобных сайтах. Формат RSS позволяет легко создавать списки статей или других информационных блоков. Информацию, поступающую по нескольким каналам RSS можно агрегировать, в результате получается отформатированный список статей, историй и т.д. вместе с заголовком и соответствующим URL, кратким содержанием, а также классификаторами для каждой из составных частей. Кроме этого RSS-лента содержит информацию для классифицирования всего канала как единого целого.

RSS - это всего лишь один из многих XML-форматов для объединения данных из различных источников. Несмотря на то, что базовая структура файла 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>

Заголовочная секция содержит общую информацию о ленте, как, например, название блога (MCslp) или же общее описание материала ("Новости из конторы Мартина Брауна").

Также в листинге 3 представлена дополнительная информация о ленте, такая как дата публикации, соответствующая дате генерации в случае динамических RSS-каналов. Кроме этого присутствует отдельный блок, описывающий каждую статью в ленте.

В подобных блоках содержится заголовок статьи, краткая резюмирующая информация, а также более детальная (или даже полная) версия материала. Последнее возможно при условии поддержки со стороны RSS-канала. Формат статьи представлен в листинге 4.

Листинг 4. Элемент RSS-ленты в XML

                
<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-преобразования

Один из простейших способов преобразования XML-файла RSS в формат VXML заключается в использовании шаблона XSL. Правда, в некоторых случаях это может оказаться непростым занятием, особенно из-за ограниченных и несколько запутанных методов для выборки элементов и логического ветвления в XSL. Но в целом это неплохой вариант для быстрого решения проблемы.

В листинге 5 показана простая таблица стилей XSL, преобразующая элементы title и description исходного файла RSS в формат VXML. В результате заголовки и содержимое каждой из статей RSS-ленты могут быть озвучены.

Листинг 5. Таблица стилей XSL для преобразования RSS в VXML

                
<?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 обрабатывает все статьи в RSS-ленте, используя для их выборки выражения XPath. Затем значения элементов title и description (заголовка и содержимого соответственно) помещаются в блоки prompt, разделяемые блоками break. Последние используются в VoiceXML для вставки пауз в вывод TTS.

Применить XSL-преобразование к XML-документу RSS можно с помощью утилиты xsltproc. Результат преобразования новостной ленты BBC показан в листинге 6.

Листинг 6. Создание файла VXML с помощью таблицы стилей XSL

                
$ 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>

Как видно из листинга, каждой статье RSS-ленты соответствует отдельный блок с заголовком, паузой и кратким содержанием. Далее сгенерированный VXML-документ можно загрузить в голосовой браузер и прослушать. Но сам по себе этот результат не особенно применим на практике, т.к. кроме этого необходимо автоматически загружать и преобразовывать содержимое RSS-ленты. Другими словами, надо подумать, как динамически предоставлять всю необходимую информацию в VoiceXML.

Процесс преобразования RSS в VoiceXML

Одна из проблем при использовании XSL - это простота и, как следствие, ограниченность этого подхода, причем как в смысле способов создания файла VXML, так и в смысле его содержимого. Для решения этой проблемы можно использовать языки программирования, например, Java или Python, позволяющие выполнять более сложные преобразования.

Прежде чем начать, представим себе логику работы простого RSS-браузера, как показано на рисунке 1.

Рисунок 1. Логика работы приложения для преобразования RSS в VXML
Workflow for an RSS to VXML application

Принцип работы следующий: после получения входящего звонка, звонящему предоставляется выбор из доступных RSS-каналов. Как только он выбрал канал, его содержимое обрабатывается и создается список заголовков всех статей в ленте. Таким образом абонент может выбрать и прослушать интересующие его статьи.

Вначале происходит обработка RSS-ленты и формирование файла VXML для прочтения вслух статей и создания основного набора информации для озвучивания.

Преобразование RSS в VoiceXML с помощью Perl

Существует множество Perl-модулей для обработки новостных лент, наиболее удобным из которых является XML::FeedPP. Модуль может обрабатывать любую новостную ленту, включая такие форматы, как RSS, RDF и Atom. Кроме этого модуль предоставляет доступ к содержимому ленты через упрощенный интерфейс.

Для разбора содержимого необходимо создать объект типа XML::FeedPP и передать ему URL ленты: my $feed = XML::FeedPP->new($feedurl);.

Остальная информация о ленте, включая заголовок, может быть получена через этот объект верхнего уровня. Например, заголовок получается следующим образом: $feed->title();.

Доступ непосредственно к статьям ленты осуществляется в два этапа: сначала необходимо получить список статей с помощью метода get_item(), а затем обращаться к их содержимому, вызывая методы title(), description() и т.д.

В листинге 7 показано, как можно составить список новостей для данной ленты.

Листинг 7. Пример создания файла VXML из RSS-ленты с помощью 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) = ('');

$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 состоит из двух основных частей. Сначала скрипт получает на вход содержимое ленты и создает блоки prompt на каждый новостной заголовок, сохраняя вывод в строковой переменной. Затем сформированная строка целиком записывается в файл VXML.

Скрипт перебирает все новости в ленте и добавляет в выходной файл заголовки, разделенные короткими паузами. Конечно для этой задачи можно было использовать полнофункциональный генератор XML, но поскольку файл имеет достаточно простую структуру, то достаточно простых операторов вывода для добавления в него информации.

Отметьте, что на данном этапе RSS-лента создана вручную, а результаты преобразования выдаются в стандартный поток вывода. Таким образом, если требуется создать выходной файл, то в него надо просто перенаправить стандартный вывод. Полученный файл VXML представлен в листинге 8 (файл сокращен в интересах экономии места).

Листинг 8. Пример VXML-файла, сгенерированного автоматически из RSS-ленты

                
<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 и позвонить, то в ответ можно услышать список новостей, полученных непосредственно из ленты RSS.

Пока что можно только прослушать список заголовков новостей. Конечно, было бы желательно кроме заголовков иметь возможность послушать и подробности выбранных новостей.

Добавление возможности выбора новостей

Чтобы добавить возможности выбора новостей и прослушивания их кратких версий (summary), необходимо предусмотреть несколько дополнительных действий на этапе создания файла VXML, а именно:

  1. Создать пронумерованный список для выбора новостей и предусмотреть возможность выбора элементов из списка.
  2. Реализовать возможность обработки входного параметра, представляющего собой номер новости для прослушивания. Параметр может быть в голосовом виде, но в данном случае мы будем использовать формат DTMF.
  3. Поместить краткие версии новостей в файл VXML.

Реализовать первое действие достаточно просто: все что требуется - это присвоить уникальный номер каждой новости и выводить его рядом с заголовком. Кроме этого, необходимо, чтобы пронумерованные заголовки точно соответствовали содержанию новостей, выдаваемым по запросу пользователя. Для этого будут использоваться две временных строковых переменных: одна будет хранить номер выбранной новости, а другая - текст, который будет озвучен для пользователя.

Для получения и обработки ввода с клавиатуры можно использовать специальный элемент field, предусмотренный в VXML: <field name="select_num" type="digits">.

В этом случае система будет ожидать числового ввода от пользователя. После этого произойдет разбор числового блока в VXML, и полученное число будет использовано для выбора нужной новости. Затем краткое содержание новости будет произнесено вслух голосовым сервисом.

Разбор числового значения происходит следующим образом: все цифры числа последовательно вводятся в виде сигналов DTMF (Dual Tone Multi-Frequency), а затем помещаются в специальную переменную: <assign name="selection" expr="select_num"/>.

Таким образом, можно получать и обрабатывать многозначные числа, хотя в нашем примере пользователь сможет выбирать только из шести последних новостей.

Как только цифры введены, можно использовать обычный блок условного перехода (if) для выбора новости для прослушивания.

Полный вариант скрипта (вновь на Perl) показан в листинге 9.

Листинг 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/);

# Выведем только 6 последний новостей

    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-лента загружается динамически, скрипт по-прежнему генерирует статический файл VXML, содержащий новости для прослушивания. Для полной интерактивности необходимо создавать его на лету.

Создание VoiceXML из RSS-лент с помощью сервлетов Java

До этого момента возможности выбора новостей были ограничены статическим файлом VXML. Для подлинной интерактивности, он должен генерироваться скриптом динамически по запросу пользователя. Т.к. большинство голосовых браузеров обращаются к VXML-файлам по URL, все что нужно - это разработать CGI или какое либо еще Web-приложение для их создания на лету.

Это можно реализовать с помощью Perl-скриптов, представленных на листингах 7 и 9, но необходимо позаботиться о формировании корректного заголовка HTTP-ответа (text/xml) перед выводом самого файла VXML. Все что нужно - это добавить две строчки в начало скрипта, как показано в листинге 12.

Листинг 12. Формирование HTTP-заголовка в Perl-скрипте

                
use CGI qw/:standard/;
print header(-type => 'text/xml');

С помощью этих инструкций импортируется специальная библиотека CGI, отвечающая за формирование заголовков HTTP. Остальное же содержимое вывода - это все тот же файл VXML, что и ранее.

На основе тех же принципов можно создавать VXML и на других языках программирования. В частности, в листинге 13 показан Java-сервлет, генерирующий такой же документ VXML, как и первый Perl-скрипт (см. листинг 7). Сервлет использует библиотеки Rome и JDOM для разбора RSS-ленты и создания требуемого VXML-файла.

Листинг 13. Создание голосовых RSS-лент c помощью VXML и JSP

                
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-файла, происходит обращение к RSS-ленте и создание VXML при запросе пользователя.

Далее этот сервлет вместе с необходимыми JAR-файлами для JDOM и Rome, можно развернуть в контейнере JSP, например в Apache Tomcat, и использовать его как основу вашего будущего VXML-приложения.

Динамическая генерация различных VXML-файлов

Кроме озвучивания какой либо конкретной RSS-ленты, можно предоставлять пользователю список новостных каналов для прослушивания. Это также дает возможность добавить интерактивности приложению, а именно, выбирать, какой скрипт и откуда вызывать при выборе той или иной новости пользователем.

Для начала надо создать список RSS-лент, доступных для озвучивания. В нашем примере используется обыкновенный массив, но в принципе эта информация может загружаться из некой базы данных. Далее для каждой ленты извлекается ее название и описание, создается необходимый пронумерованный список, после чего скрипт ждет выбора пользователя. Однако в этом случае, после получения от пользователя конкретного числа, скрипт вызывает внешний обработчик по URL (в данном случае, самого себя), передавая номер новостной ленты в качестве аргумента. На конечном этапе можно вызвать дополнительную функцию, выдающую список новостей конкретной ленты и позволяющую выбрать одну из них.

В листинге 14 представлен полный код CGI-скрипта на Perl, выполняющего вышеперечисленные действия.

Листинг 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
}

В примере подразумевается, что один параметр используется как для вывода всего списка лент, так и для выбора конкретной ленты из списка. В случае использования базы данных, скорее всего, потребуется присваивать уникальный идентификатор каждой ленте и использовать его для выбора. Но общий принцип остается неизменным.

Кстати говоря, необходимого результата можно достичь используя только VXML. В VXML можно добавлять различные блоки form, а затем использовать их для вывода нужных лент. Но на практике создавать VXML-файлы большого размера, как правило, не стоит, гораздо лучше использовать скрипты для быстрой генерации небольших объемов VXML-данных на лету.

Используя такую динамическую генерацию VXML, следует позаботиться о том, чтобы она не замедляла работу приложения. Например, вышеприведенный скрипт непосредственно обращается к каждой новостной ленте, что может занимать значительное время. Используя базу данных, можно загружать и выводить заголовок и краткое описания лент намного быстрее.

Кроме этого, имейте в виду, что в случае динамической генерации могут потребоваться дополнительные усилия по обработке возможных ошибок. Например, в нашем случае если какая-либо из лент недоступна, то скрипт выведет пустой список новостей, в то время как сообщение типа: "Извините, сервис в настоящее время недоступен" было бы значительно информативнее для пользователя при возникновении такой ситуации.

Заключение

В данной статье были рассмотрены различные подходы к генерированию документов VXML, необходимых для прослушивания RSS-лент. Мы начали с простого варианта на основе XSL, а затем перешли к более гибким решениям, использующим скрипты Perl и сервлеты Java.

Сами по себе скрипты достаточно примитивны. Вся мощь заключается в сгенерированном VXML-содержимом, а также в голосовом браузере, использующем VXML для предоставления голосового интерфейса. Кроме этого, в статье рассказывалось как обеспечивать определенный уровень интерактивности, используя VXML совместно с динамическими скриптами. Все это позволяет относительно легко разрабатывать достаточно сложные голосовые приложения.

Оставайтесь с нами, и во второй части мы разработаем календарь с голосовой поддержкой.


Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=16891