Алексей Валиков - Технология XSLT
<item source="a" name="H"/>
</source>
Составные ключи
В теории реляционных баз данных существует такое понятие, как составной ключ. Согласно определению К. Дж. Дейта [Дейт 1999], составной ключ — это "потенциальный ключ; состоящий из более чем одного атрибута".
Хотя концепция ключей в XSLT сильно отличается от того, что называется ключом в реляционных БД, идея весьма и весьма интересна: использовать при обращении к множествам узлов не одно свойство, а некоторую их комбинацию.
Главная проблема состоит в том, что значение ключа в XSLT всегда является строкой, одним из самых примитивных типов. И выбирать множества узлов можно только по одному строковому значению за один раз. Ничего похожего на key(key-name, key-value-1, key-value-2, ...) для выбора узлов, первое свойство которых равно key-value-1, второе — key-value-2 и так далее, XSLT не предоставляет.
Выход достаточно очевиден: если значение ключа не может быть сложной структурой, оно должно выражать сложную структуру. Иными словами, раз значением составного ключа может быть только строка, то эта строка должна состоять из нескольких частей.
ПримерПредположим, что объекты с одинаковыми именами могут принадлежать различным источникам. Покажем, как с помощью ключей можно решить следующие задачи:
□ найти объект с определенным именем и источником;
□ найти объекты с определенным именем;
□ найти объекты с определенным источником.
Листинг 8.27. Входящий документ<items>
<item source="a" name="A"/>
<item source="b" name="B"/>
<item source="b" name="E"/>
<item source="b" name="F"/>
<item source="a" name="C"/>
<item source="c" name="G"/>
<item source="a" name="H"/>
<item source="a" name="B"/>
<item source="b" name="G"/>
<item source="c" name="H"/>
<item source="c" name="C"/>
<item source="c" name="D"/>
<item source="b" name="A"/>
<item source="a" name="B"/>
<item source="c" name="D"/>
<item source="c" name="E"/>
<item source="a" name="F"/>
</items>
Для элементов item мы будем генерировать ключи, значения которых будут состоять из двух частей — источника и имени, разделенных символом "-". Для того чтобы решить одним ключом все три поставленные задачи, мы будем использовать для его определения три элемента xsl:key.
Листинг 8.28. Входящий документ<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="src" match="item" use="concat(@source,'-')"/>
<xsl:key name="src" match="item" use="concat('-', @name)"/>
<xsl:key name="src" match="item" use="concat(@source, '-', @name)"/>
<xsl:template match="/">
<result>
<items source="a" name="B">
<xsl:copy-of select="key('src', 'a-B')"/>
</items>
<items name="B">
<xsl:copy-of select="key('src', '-B')"/>
</items>
<items source="a">
<xsl:copy-of select="key('src', 'a-')"/>
</items>
</result>
</xsl:template>
</xsl:stylesheet>
Листинг 8.29. Выходящий документ<result>
<items source="a" name="B">
<item source="a" name="B"/>
<item source="a" name="B"/>
</items>
<items name="B">
<item source="b" name="B"/>
<item source="a" name="B"/>
<item source="a" name="B"/>
</items>
<items source="a">
<item source="a" name="A"/>
<item source="a" name="C"/>
<item source="a" name="H"/>
<item source="a" name="B"/>
<item source="a" name="B"/>
<item source="a" name="F"/>
</items>
</result>
У приведенного здесь способа формирования ключа есть определенные ограничения: необходимо иметь априорную информацию о строковых значениях каждого из свойств, составляющих наш композитный ключ для того, чтобы корректно формировать его строковые представления. Например, если бы в приведенном выше документе имена объектов и источников могли бы содержать символ "-", было бы непонятно, к какому объекту относится составной ключ "a-b-c": к объекту с источником a-b и именем с или к объекту с источником а и именем b-c. К счастью, в большинстве случаев такая информация имеется, и генерировать составные ключи не очень сложно.
Функция key в паттернах
Разбирая синтаксические правила построения паттернов, мы встретились с особой формой паттерна, в котором могла использоваться функция key. Приведем еще раз эту продукцию:
[PT3] IdKeyPattern ::= 'id' '(' Literal ')'
| 'key' '(' Literal ',' Literal ')'
Функция key(key-name, key-value) в паттерне будет соответствовать узлам, значение ключа key-name которых равняется или принадлежит объекту key-value. Это позволяет использовать возможности ключей при проверке узлов на соответствие образцу.
ПримерПредположим, что нам нужно по-особому обработать объекты, принадлежащие источнику а. Для этого мы можем создать шаблон следующего вида.
Листинг 8.30. Шаблон, использующий функцию key в паттерне<xsl:template match="key('src', 'a')">
<!-- Содержимое шаблона -->
</xsl:template>
Этот шаблон будет применяться к любым узлам, имеющим ключ src со значением а.
Нумерация
Нумерация, несомненно, является одной из самых естественных проблем, решаемых при помощи XSLT. Задача нумерации состоит в том, чтобы, исходя из позиции обрабатываемого узла в дереве документа, вычислить по заданным критериям его порядковый номер. В качестве примера такого рода задачи можно привести вывод номеров частей, разделов и глав книги, указание номеров элементов списка или строк таблицы.
Для вычисления порядковых номеров узлов в дереве в XSLT существует несколько способов. В простых случаях для достижения цели бывает достаточно воспользоваться одним из следующих XPath-выражений.
□ Для того чтобы получить порядковый номер текущего узла в обрабатываемом множестве, можно использовать функцию position. Обратим внимание, что это будет позиция узла в обрабатываемом в данный момент множестве, а не в дереве исходящего документа.
□ Функция count(preceding-sibling::*)+1 возвращает порядковый номер текущего элемента среди других элементов его родителя, иначе говоря, среди его братьев. Путь выборки preceding-sibling::* выбирает множество братских элементов, предшествующих текущему узлу, а функция count вычисляет их количество. Таким образом, значение count(preceding-sibling::*)+1 будет равно 1 для первого элемента (поскольку ему другие элементы не предшествуют), 2 — для второго (ему предшествует один элемент) и так далее.
□ Для того чтобы учитывать при подсчете только определенные элементы, можно переписать предыдущее выражение в чуть более строгом виде. Например, выражение, считающее только элементы chapter, будет задаваться следующим образом: (preceding-sibling::chapter) +1.
□ Глубина текущего узла от корня дерева может быть вычислена выражением count(ancestor-or-self::node()). Это выражение будет возвращать 1 для корневого узла, 2 для элемента документа и так далее.
Вычислять выражения и выводить вычисленные значения в результирующее дерево следует, как и обычно — при помощи элемента xsl:value-of.
Пример<xsl:value-of select="count(preceding-sibling::chapter)+1"/>
В более сложных ситуациях бывает необходимо подсчитывать узлы, находящиеся на разных уровнях вложенности или удовлетворяющие определенным условиям, начинать отсчет с заданной позиции в документе и использовать при вычислении номера сложные выражения. Использование XPath в таких случаях может быть очень неудобным — выражения будут слишком громоздкими и вычислять их придется в несколько этапов.
Другим, несравненно более легким и удобным способом нумерации и индексирования узлов является использование элемента xsl:number.
Элемент xsl:number
Синтаксис элемента описывается следующей конструкцией:
<xsl:number
level="single"
| "multiple"
| "any"
count="паттерн"
from="паттерн"
value="выражение"
format="{строка}"
lang="{токен}"