Источник: What's New in XPath 2.0
by Evan Lenz, XML.com,
Copyright ¤ 2002 O'Reilly & Associates, Inc.
Перевод на русский язык - Copyright ¤ 2002 Алексей Валиков.

XSLT 2.0 и XPath 2.0

Языки XSLT 2.0 и XPath 2.0 сильно связаны друг с другом. Эти два языка определяются раздельно и требования к их новым версиям тоже содержатся в разных документах только потому, что XPath 2.0 также предназначен для использования в окружениях, отличных от XSLT, например, в XQuery 1.0. Однако, в задачах пользователей XSLT эти два языка используются совместно. Нельзя использовать XPath 2.0 с XSLT 1.0 или XPath 1.0 с XSLT 2.0. (Во всяком случае, Консорциум W3 в настоящий момент не предполагает таких комбинаций.)

Замечание

Вы когда-нибудь интересовались тем, что случилось с версией XSLT 1.1? Ее разработка была прекращена. Официально в августе 2001 года, а в действительности за несколько месяцев до того, рабочая группа по языку XSL остановила работу над XSLT 1.1, сконцетрировав вместо этого свои усилия на разработке языков XSLT и XPath 2.0 и переместив требования, предъявленные к языку XSLT 1.1 в список требований к XSLT 2.0.

Долгожданное появление

В сообществе пользователей языка XSLT новую версию языка ожидали уже давно и с нетерпением. Как и с первыми версиями многих других языков, то, какие усовершенствования окажутся наиболее важными, не было ясно без практического опыта. С 16 ноября 1999 года, когда язык XSLT 1.0 получил статус рекомендации Консорциума W3, стало довольно очевидно, что некоторые пробелы в функциональности должны быть восполнены в следующей версии языка. В этой статье мы покажем, как XSLT 2.0 решает четыре основных проблемы из этого списка:

Смерть результирующим фрагментам деревьев!

В XSLT 1.0 тип результирующего фрагмента дерева во многом похож на множество узлов, однако, это тип "второго сорта". Результирующий фрагмент дерева это то, что получается при создании временного дерева при помощи инструкции xsl:variable. Проблема состоит в том, что после этого невозможно обращаться к внутренним частям созданного дерева при помощи XPath-выражений без использования специфичной для каждого процессора функции расширения типа node-set() для преобразования результирующего фрагмента дерева в значение "первого сорта" типа множества узлов (состоящего из единственного корневого узла дерева). Основная причина существования результирующих фрагментов деревьев это облегчение бремени реализации, однако, поскольку почти все существующие XSLT-процессоры все равно предоставляют свои версии функции расширения node-set(), это суждение стало весьма спорным. В любом случае, потребность в преодолении этого ограничения с некоторых пор стала совершенно ясной в силу того, что требовалась возможность разбивать сложные преобразования на последовательности более простых шагов.

Если вы еще не догадались, в XSLT 2.0 от результирующих фрагментов деревьев избавились. Теперь, когда вы используете xsl:variable для того, чтобы создать временное дерево, значение этой переменной - исинное множество узлов. Фактически, в терминах XPath 2.0, это истинная последовательность узлов, состоящая из узла документа, который в XPath 2.0 есть ни что иное, как "корневой узел" из XPath 1.0. К этой последовательности можно применять путевые выражения для того, чтобы проходить дерево вглубь, применять к нему шаблонные правила и так далее, точно также, как вы бы работали с любым исходным документом. В XSLT 2.0 потребность в функции расширения node-set() отпадает.

Использование нескольких выходящих документов

Другим расширением, которое предлагают многие процессоры XSLT 1.0 это поддержка нескольких выходящих документов. Это расширение оказалось очень полезным, особенно в случае статической генерации web-сайтов, содержащих множество страниц. Проблема с расширениями в том, что они не стандартны. Каждый из XSLT-процессор имеет собственный элемент для выполнения задачи, например, saxon:output, xt:document, и т.д.

XSLT 2.0 предлагает стандартный способ вывода в несколько документов с использованием элемента xsl:result-document. В следующем примере преобразование создает несколько выходящих документов: один "основной выходящий документ" и неопределенное число "вторичных выходящих документов". Основной выходящий документ будет выведен как XHTML, а вторичные выходящие документа - как текст.

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">

<xsl:output method="xhtml"/>
<xsl:output method="text" name="textFormat"/>

<xsl:template match="/">
<html>
<head>
<title>Ссылки на текстовые документы </title>
</head>
<body>
<p>Список ссылок на текстовые файлы:</p>
<ul>
<xsl:apply-templates select="//textBlob"/>
</ul>
</body>
</html>
</xsl:template>

<xsl:template match="textBlob">
<xsl:variable name="uri" select="concat('text', position(), '.txt')"/>
<li>
<a href="{$uri}">
<xsl:value-of select="$uri"/>
</a>
</li>
<xsl:result-document href="{$uri}" format="textFormat">
<xsl:value-of select="."/>
</xsl:result-document>
</xsl:template>

</xsl:stylesheet>

Атрибут href элемента xsl:result-document используется для задания универсального идентификатора ресурса (URI) соответствующего выходящего документа. Для многих процессоров это будет означать запись в файл с заданным в этом атрибуте именем. Атрибут format передает именованное определение формата вывода. В нашем случае, он указывает на элемент xsl:output, которому, соответственно, дано имя textFormat.

Еще одна достойная упоминания особенность этого примера в использовании впервые представленного в XSLT 2.0 метода вывода XHTML.

Упрощенная группировка.

В XSLT 1.0 не было встроенной поддержки группировки. Некоторые проблемы группировки, конечно же, решались различными методиками группировки (например, методом Мюнха), однако такие решения как правило довольно сложны и громоздки. Одним из требований к XSLT 2.0 была упрощенная группировка. Как мы увидим из следующего несложного примера, эта цель практически достигнута.

Пример, который использован и в списке требований к XSLT 2.0 и в черновом варианте спецификации языка, заключается в преобразование списка городов из следующего простого XML-документа

<cities>
<city name="milan" country="italy" pop="5"/>
<city name="paris" country="france" pop="7"/>
<city name="munich" country="germany" pop="4"/>
<city name="lyon" country="france" pop="2"/>
<city name="venice" country="italy" pop="1"/>
</cities>

в HTML-таблицу, которая группирует города по странам, в которых они находятся:

<table>
<tr>
<th>Country</th>
<th>City List</th>
<th>Population</th>
</tr>
<tr>
<td>italy</td>
<td>milan, venice</td>
<td>6</td>
</tr>
<tr>
<td>france</td>
<td>paris, lyon</td>
<td>9</td>
</tr>
<tr>
<td>germany</td>
<td>munich</td>
<td>4</td>
</tr>
</table>

Сложная часть этого преобразования заключается в генерировании последних трех строк таблицы (выделенных полужирным шрифтом). Решение на XSLT 1.0 показано ниже:

  <xsl:for-each select="cities/city[not(@country =
preceding::*/@country)]">
<tr>
<td><xsl:value-of select="@country"/></td>
<td>
<xsl:for-each select="../city[@country = current()/@country]">
<xsl:value-of select="@name"/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
</td>
<td><xsl:value-of select="sum(../city[@country =
current()/@country]/@pop)"/></td>
</tr>
</xsl:for-each>

В приведенном выше примере мы сначала находим первый город каждой из стран при помощи следующего XPath-выражения:

cities/city[not(@country = preceding::*/@country)]

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

../city[@country = current()/@country]

Очевидно, это далеко не идеальное решение, поскольку избыточность делает его довольно сильно подверженным ошибкам. Ввод в XSLT 2.0 элемента xsl:for-each-group, решает большинство из проблем, связанных с группировкой. Следующий пример показывает, насколько более просто решается такая задача в XSLT 2.0 (новые элементы выделены полужирным шрифтом):

  <xsl:for-each-group select="cities/city" group-by="@country">
<tr>
<td><xsl:value-of select="@country"/></td>
<td>
<xsl:value-of select="current-group()/@name" separator=", "/>
</td>
<td><xsl:value-of select="sum(current-group()/@pop)"/></td>
</tr>
</xsl:for-each-group>

В приведенном выше примере элемент xsl:for-each-group инициализирует "текущую группу" как часть контекста вычисления XPath-выражений. Текущая группа это просто-напросто последовательность. После того, как мы подготовили нашу группу, используя атрибут group-by, мы можем обращаться к ней, используя функцию current-group(). Это немедленно исключает избыточность, которая присутствовала в решении на XSLT 1.0.

Также обратите внимание на атрибут separator элемента xsl:value-of. Одно лишь присутствие этого атрибута дает процессору команду вывести не только строковое значение первого члена последовательности (как это было в XSLT 1.0's), а строковые значения всех членов последовательности в соответствующем порядке. Значение необязательного атрибута separator это строка, которая используется, как разделитель строковых значений элементов в результате. Ради обратной совместимости с XSLT 1.0, в случае, когда атрибут separator не задан, выводится строковое значение только лишь первого члена последовательности.

Наконец, элемент xsl:for-each-group способен разрешать различные типы проблем группировки, одного из следующих трех атриубутов: group-by (который мы только что выдели в деле), group-adjacent (который позволяет группировать по узлы по признаку их смежности в документе, например, для преобразования линейных структур с эелементами <para> в блочные), и group-starting-with (который группирует по признаку соответствия элементов последовательности определенному образцу). Примеры применения каждого из этих атрибутов можно найти в последнем черновом варианте спецификации языка XLST в секции "13.3 Примеры группировки".

Функции, определенные пользователем

XSLT 2.0 предоставляет пользователем возможность определять их собственные функции, которые затем могут использоваться в XPath-выражениях. Это чрезвычайно мощный механизм, который, скорее всего, будет очень полезен. Так называемые функции-преобразования определяются при помощи элемента xsl:function. Этот элемент имеет один обязательный атрибут name, задающий имя функции. Элемент xsl:function также содержит несколько элементов xsl:param, за которыми следуют несколько элементов xsl:variable, и в затем - ровно один эелемент xsl:result. Такая жесткая модель содержимого может показаться слишком ограничивающей, но тем не менее ее настоящая сила обнаруживается в использовании выражений языка XPath 2.0 в атрибуте select элемента xsl:result для вычисления результата. Как вы. Возможно, помните, XPath 2.0 включает возможность вычисления условных выражений (конструкция if...then) и итераций (конструкция for...return).

Как показывает следующий пример (взятый прямо из последнего чернового варианта спецификации), большая часть работы выполняется внутри атрибута select элемента xsl:result. Это преобразование вызывает определенную пользователем рекурсивную фуркцию str:reverse() для того, чтобы вывести строку "MAN BITES DOG".

<xsl:transform 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://user.com/namespace"
version="2.0"
exclude-result-prefixes="str">

<xsl:function name="str:reverse">
<xsl:param name="sentence"/>
<xsl:result
select="if (contains($sentence, ' '))
then concat(str:reverse(substring-after($sentence, ' ')),
' ',
substring-before($sentence, ' '))
else $sentence"/>
</xsl:function>

<xsl:template match="/">
<output>
<xsl:value-of select="str:reverse('DOG BITES MAN')"/>
</output>
</xsl:template>

</xsl:transform>

Другие полезные новшества

В XSLT 2.0 включено множество других полезных возможностей, которые мы не будем углубляться. В их числе механизм для задания пространства имен по умолчанию для XPath-выражений, возможность использования переменных в предикатах паттернов соответствия, именованные определения сортировки, возможность чтения внешних файлов как неразобранного текста и так далее.

Кроме того, большую часть спецификации XSLT 2.0 еще предстоит написать. В частности, материал, посвященный созданию и копированию содержимого, тип которого определен при помощи языка XML Schema. Об этом в последнем рабочем черновике сказано следующее: "Эта работа все еще не закончена. Средства связывания информации о типах данных с созданными элементами и атрибутами, скорее всего появятся в следующих черновых вариантах спецификации XSLT 2."

Засучим рукава

Для тех из вас, кому не терпится попробовать что-нибудь из новых возможностей, Майкл Кей (Michael Kay) выпустил новую версию своего процессора, Saxon 7.0, который включает "экспериментальную реализацию XSLT 2.0 и XPath 2.0". Этот процессор реализует множество возможностей, представленных в черновых спецификациях языков XSLT 2.0 и XPath 2.0; особое внимание уделено тем новшествам, который, по всей видимости, не будут меняться. Я протестировал на Saxon 7.0 каждый пример, предложенный в этой статье, и они все выполнились как и положено.

Работа над XSLT 2.0 все еще не закончена и следует сделать предупреждение о том, что многие вещи могут измениться с настоящего момента до того времени, когда спецификация получит статус технической рекомендации Консорциума W3. До тех пор, редакторы спецификации будут благодарны за критику и отзывы, направленные по адресу xsl-editors@w3.org.