xsl:key¶
Элемент xsl:key
определяет в преобразовании именованный ключ.
Элемент верхнего уровня xsl:key
определяет в преобразовании ключ именем, заданным в значении обязательного атрибута name
, значением которого для каждого узла документа, соответствующего обязательному паттерну match
, будет результат вычисления выражения, заданного в обязательном атрибуте use
. Ни атрибут use
, ни атрибут match
не могут содержать переменных.
Синтаксис¶
1 |
|
Атрибуты:
name
- обязательный атрибут, определяет имя ключа.
match
- обязательный атрибут, выражение XPath, которое определяет узлы, индексируемые по данному ключу.
use
- обязательный атрибут, выражение XPath, которое определяет свойство индексируемых узлов, используемое для выборки узлов из индекса.
Описание и примеры¶
Прежде чем мы приступим к разбору ключей, которые являются одной из самых мощных концепций языка XSLT, попробуем решить одну несложную задачку.
Листинг 8.19. Входящий документ
1 2 3 4 5 6 7 8 9 10 |
|
Пусть входящий документ представляет собой список объектов (элементов item
), каждый из которых имеет имя (атрибут name
) и источник (атрибут source
). Требуется сгруппировать объекты по своим источникам и получить документ приблизительно следующего вида.
Листинг 8.20. Требуемый результат
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Первым шагом на пути решения этой задачи является формулировка в терминах XSLT предложения "сгруппировать объекты по своим источникам". Источник каждого объекта определяется его атрибутом source
, значит множество объектов, принадлежащих одному источнику "а
", будет определяться путем выборки
1 |
|
Тогда для каждого элемента item
в его группу войдут элементы, которые будут выбраны выражением
1 |
|
Попробуем использовать этот факт в следующем шаблоне:
1 2 3 4 5 6 7 |
|
Как и ожидалось, при применении этого правила к элементам item
для каждого из них будет создана группа, принадлежащая тому же источнику, — уже хороший результат, но в условии требуется создать по группе не для каждого объекта, а для каждого источника. Чтобы достичь этого, можно создавать группу только для первого объекта, принадлежащего ей. Провести такую проверку опять же несложно: объект будет первым в группе тогда и только тогда, когда ему не предшествуют другие, элементы item
, принадлежащие тому же источнику. Иначе говоря, создаем группы только для тех элементов, для которых выражение
1 |
|
будет возвращать пустое множество.
С небольшими добавлениями искомое преобразование целиком будет иметь вид.
Листинг 8.21. Преобразование
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Бесспорно, решение было несложным, но довольно громоздким. Самым же узким местом в этом преобразовании является обращение к элементам item
источника текущего элемента посредством сравнения атрибутов source
.
Проблема совершенно стандартна для многих преобразований: нужно выбирать узлы по определенным признакам, причем делать это нужно как можно более эффективно. Хорошо, что в нашем документе было всего восемь элементов item
, но представьте себе ситуацию, когда элементов действительно много.
Проблема, которую мы подняли, достаточно серьезна. Она состоит в оптимизации поиска узлов с определенными свойствами в древовидно организованной структуре.
Попробуем разобраться в смысле фразы "узел обладает определенными свойствами". Очевидно, это означает, что для этого узла выполняется некое логическое условие, иначе говоря, некий предикат обращается в "истину".
Однако какого именно типа условия мы чаще всего проверяем? Анализируя различные классы задач, можно придти к выводу, что в большинстве случаев предикаты являются равенствами — выражениями, которые обращаются в "истину" тогда и только тогда, когда некоторый параметр узла, не зависящий от текущего контекста, равен определенному значению. В нашем примере смысл предиката на самом деле состоит не в том, чтобы проверить на истинность выражение @source=current()/@source
, а в том, чтобы проверить на равенство @source
и current()/@source
.
Если переформулировать это для общего случая, то нам нужно выбрать не те узлы, для которых истинно выражение A=B
, скорее нужно выбрать те, для которых значение A
равно значению B
. Иначе говоря, узел будет идентифицироваться значением B
своего свойства A
. И если мы заранее вычислим значения свойств A
, проблема поиска узлов в дереве сведется к классической проблеме поиска элементов множества (в нашем случае — узлов дерева) по определенным значениям ключей (в нашем случае — значениями свойств A
).
Чтобы пояснить это, вернемся к нашему примеру: мы ищем элементы item
со значением атрибута source
, равным заданному. Свойством, идентифицирующим эти элементы, в данном случае будут значения их атрибутов source
, которые мы можем заранее вычислить и включить в табл. 8.2.
Таблица 8.2. Значения атрибута source
элементов item
Идентификатор (значение атрибута source) | Элемент item |
---|---|
a | <item source="a" name="A"/> |
a | <item source="a" name="C"/> |
a | <item source="a" name="H"/> |
b | <item source="b" name="B"/> |
b | <item source="b" name="E"/> |
b | <item source="b" name="F"/> |
c | <item source="c" name="D"/> |
c | <item source="c" name="G"/> |
Таким образом, значение "c
" идентифицирует объекты с именами D
и G
, а значение "a
" — объекты с именами A
, C
и H
, причем находить соответствующие элементы в таблице по их ключевому свойству не составляет никакого труда.
Несмотря на то, что произведенные нами манипуляции чрезвычайно просты (и настолько же эффективны), процессор вряд ли в общем случае сможет сделать что-либо подобное сам, и потому очень важной является возможность явным образом выделять в XSLT-преобразованиях ключевые свойства множеств узлов.
В этом разделе мы будем рассматривать две конструкции, позволяющие манипулировать множествами узлов посредством ключей — это элемент xsl:key
, который определяет в преобразовании именованный ключ, и функция key
, которая возвращает множество узлов, идентифицирующихся заданными значениями ключей.
Пример 1
В нашем примере элементы item
идентифицируются значениями своих атрибутов source
. Для их идентификации мы можем определить ключ с именем src
следующим образом:
1 |
|
Следуя строгому определению, данному в спецификации языка, ключом называется тройка вида (node, name, value)
, где node
— узел, name
— имя и value
— строковое значение ключа. Тогда элементы xsl:key
, включенные в преобразование, определяют множество всевозможных ключей обрабатываемого документа. Если этому множеству принадлежит ключ, состоящий из узла x
, имени y
и значения z
, говорят, что узел x
имеет ключ с именем y
и значением z
или что ключ y
узла x
равен z
.
Пример 2
Ключ src
из предыдущего примера определяет множество, которое состоит из следующих троек:
1 2 3 4 5 6 |
|
В соответствии с нашими определениями мы можем сказать, что элемент
1 |
|
имеет ключ с именем "src
" и значением "b
" или что ключ "src
" элемента
1 |
|
равен "a
".
Для того чтобы обращаться к множествам узлов по значениям их ключей, в XSLT существует функция key
, о которой мы сейчас и поговорим.
Функция key()¶
Ниже приведена синтаксическая конструкция данной функции:
1 |
|
Итак, элементы xsl:key
нашего преобразования определили множество троек (node, name, value)
. Функция key( key-name, key-value )
выбирает все узлы x
такие, что значение их ключа с именем key-name
(первым аргументом функции) равно key-value
(второму аргументу функции).
Пример 3
Значением выражения key('src', 'a')
будет множество элементов item
таких, что значение их ключа "src
" будет равно "a
". Попросту говоря, это будет множество объектов источника "a
".
Концепция ключей довольно проста, и существует великое множество аналогий в других языках программирования: от хэш-функций до ключей в реляционных таблицах баз данных. По всей вероятности, читателю уже встречалось что-либо подобное.
Но не следует забывать, что язык XSLT — довольно нетрадиционный язык и с точки зрения синтаксиса, и с точки зрения модели данных. Как следствие, ключи в нем имеют довольно много скрытых нюансов, которые очень полезно знать и понимать. Мы попытаемся как можно более полно раскрыть все эти особенности.
Определение множества ключей¶
Не представляет особой сложности определение множества ключей в случае, если в определении они идентифицируются строковыми выражениями. Например, в следующем определении
1 |
|
атрибут use
показывает, что значением ключа src
элемента item
будет значение атрибута source
. Но что можно сказать о следующем определении:
1 |
|
Очевидно, это уже гораздо более сложный, но, тем не менее, вполне реальный случай, не вписывающийся в определения, которые давались до сих пор. Мы говорили лишь о том, что множество ключей определяется элементами xsl:key
преобразования, но как именно оно определяется — оставалось доселе загадкой. Восполним этот пробел, дав строгое определение множеству ключей.
Узел x
обладает ключом с именем y
и строковым значением z
тогда и только тогда, когда в преобразовании существует элемент xsl:key
такой, что одновременно выполняются все нижеперечисленные условия:
- узел
x
соответствует паттерну, указанному в его атрибутеmatch
; - значение его атрибута
name
равно имениy
; - результат
u
вычисления выражения, указанного в значении атрибутаuse
в контексте текущего множества, состоящего из единственного узлаx
, удовлетворяет одному из следующих условий: u
является множеством узлов иz
равно одному из их строковых значений;u
не является множеством узлов иz
равно его строковому значению.
Без сомнения, определение не из простых. Но как бы мы действовали, если бы физически создавали в памяти множество ключей? Ниже представлен один из возможных алгоритмов:
- для каждого элемента
xsl:key
найти множество узлов документа, удовлетворяющих его паттернуmatch
(множествоX
); - для каждого из найденных узлов
(x ? X)
вычислить значение выражения атрибутаuse
(значениеu(x)
); - если
u(x)
является множеством узлов (назовем егоUx
), то для каждогоuxi ? Ux
создать ключ(x, n, string(uxi))
, гдеn
— имя ключа (значение атрибутаname
элементаxsl:key
); - если
u(x)
является объектом другого типа (назовем егоux
), создать ключ(x, n, string(ux))
.
Пример 4
Найдем множество ключей, создаваемое определением
1 |
|
Имена всех ключей будут одинаковы и равны "src
". Множество x
узлов, удовлетворяющих паттерну item
, будет содержать все элементы item
обрабатываемого документа. Значением выражения, заданного в атрибуте use
, будет множество всех узлов атрибутов каждого из элементов item
. Таким образом, множество узлов будет иметь следующий вид:
1 2 3 4 5 6 7 8 9 10 11 |
|
В итоге функция key('src', 'a')
будет возвращать объекты с именами A
, C
и H
, а функция key('src', 'A')
— единственный объект с именем A
(поскольку ни у какого другого элемента item
нет атрибута со значением "A
").
Необходимо сделать следующее замечание: совершенно необязательно, чтобы процессор действительно физически создавал в памяти множества ключей. Это множество определяется чисто логически — чтобы было ясно, что же все-таки будет возвращать функция key
. Процессоры могут вычислять значения ключей и искать узлы в документе и во время выполнения, не генерируя ничего заранее. Но большинство процессоров, как правило, все же создают в памяти определенные структуры для манипуляций с ключами. Это могут быть хэш-таблицы, списки, простые массивы или более сложные нелинейные структуры, упрощающие поиск, — важно другое. Важно то, что имея явное определение ключа в xsl:key
, процессор может производить такую оптимизацию.
Использование нескольких ключей в одном преобразовании¶
В случае, когда к узлам в преобразовании нужно обращаться по значениям различных свойств, можно определить несколько ключей — каждый со своим именем. Например, если мы хотим в одном случае обращаться к объектам, принадлежащим одному источнику, а во втором — к объектам с определенными именами, мы можем определить в документе два ключа — один с именем src
, второй — с именем name
:
1 2 |
|
Множество ключей, созданных этими двумя определениями, будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 |
|
В этом случае функция key('src', 'a')
возвратит объекты с именами A
, C
и H
, а функция key('name', 'A')
— объект с именем A
.
Имя ключа является расширенным именем. Оно может иметь объявленный префикс пространства имен, например
1 2 3 4 5 6 |
|
В этом случае функция key(key-name, key-value)
будет возвращать узлы, значение ключа с расширенным именем key-name
которых равно key-value
. Совпадение расширенных имен определяется как обычно — по совпадению локальных частей и URI
пространств имен.
Использование нескольких определений одного ключа¶
Процессор должен учитывать все определения ключей данного преобразования — даже если некоторые из них находятся во включенных или импортированных модулях. Порядок импорта элементов xsl:key
не имеет значения: дело в том, что определения ключей с одинаковыми именами для одних и тех же узлов, но с разными значениями ключа не переопределяют, а дополняют друг друга.
Пример 5
Предположим, что в нашем документе имеется несколько элементов item
, в которых не указано значение атрибута source
, но по умолчанию мы будем причислять их к источнику a
. Соответствующие ключи будут определяться следующим образом:
1 2 |
|
То есть для тех элементов item
, у которых есть атрибут source
, значением ключа будет значение этого атрибута, для тех же элементов, у которых атрибута source
нет, его значением будет "a
".
Для входящего документа вида
1 2 3 4 5 6 7 8 9 10 11 |
|
соответствующее множество ключей будет определяться следующим образом:
1 2 3 4 5 6 7 8 9 |
|
Функция key('src', 'a')
возвратит объекты с именами A
, C
, H
, I
, J
и K
.
То, что одни и те же узлы могут иметь разные значения одного ключа, является также очень удобным свойством. Например, два определения ключей, приведенные выше, можно дополнить третьим:
1 2 3 4 5 |
|
Это определение позволит по значению "#default
" обращаться к объектам, принадлежащим источнику по умолчанию.
Использование множеств узлов в функции key¶
Функция key
принимает на вход два аргумента: первым аргументом является строка, задающая имя ключа, в то время как вторым аргументом может быть объект любого типа. В том случае, если аргумент key-value
в функции key(key-name, key-value)
является множеством узлов, функция key
возвратит все узлы, имеющие ключ key-name
со значением, равным хотя бы одному из строковых значений узла множества key-value
.
Пример 6
Предположим, что источники объектов будут сгруппированы следующим образом:
1 2 3 4 |
|
Для того чтобы вычислить множество элементов item
, принадлежащих любому из источников данной группы, достаточно будет воспользоваться выражением вида
1 |
|
Множество узлов, выбираемое путем sources/source/@name
, будет содержать атрибуты name
элементов source
. Их строковые значения будут равны a
и c
, значит, наше выражение возвратит множество элементов item
, значение атрибута source
которых равно либо a
либо c
.
Использование ключей в нескольких документах¶
Ключи, определенные в преобразовании, могут использоваться для выбора узлов в различных обрабатываемых документах. Функция key
возвращает узлы, которые принадлежат текущему документу, то есть документу, содержащему текущий узел. Значит, для того, чтобы выбирать узлы из внешнего документа, необходимо сделать текущим узлом один из узлов этого внешнего документа. Контекстный документ может быть легко изменен элементом xsl:for-each
, например, для того, чтобы текущим документом стал документ a.xml
, достаточно написать
1 2 3 |
|
Пример 7
Предположим, что нам нужно выбрать объекты, принадлежащие источнику a
, причем принадлежность объектов определена в двух внешних документах, a.xml
и b.xml
.
Листинг 8.22. Входящий документ
1 |
|
Листинг 8.23. Документ a.xml
1 2 3 4 5 6 |
|
Листинг 8.24. Документ b.xml
1 2 3 4 5 6 |
|
Листинг 8.25. Преобразование
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Листинг 8.26. Выходящий документ
1 2 3 4 5 |
|
Составные ключи¶
В теории реляционных баз данных существует такое понятие, как составной ключ. Согласно определению К. Дж. Дейта [Дейт 1999], составной ключ — это "потенциальный ключ; состоящий из более чем одного атрибута".
Хотя концепция ключей в XSLT сильно отличается от того, что называется ключом в реляционных БД, идея весьма и весьма интересна: использовать при обращении к множествам узлов не одно свойство, а некоторую их комбинацию.
Главная проблема состоит в том, что значение ключа в XSLT всегда является строкой, одним из самых примитивных типов. И выбирать множества узлов можно только по одному строковому значению за один раз. Ничего похожего на key(key-name, key-value-1, key-value-2, ...)
для выбора узлов, первое свойство которых равно key-value-1
, второе — key-value-2
и так далее, XSLT не предоставляет.
Выход достаточно очевиден: если значение ключа не может быть сложной структурой, оно должно выражать сложную структуру. Иными словами, раз значением составного ключа может быть только строка, то эта строка должна состоять из нескольких частей.
Пример 8
Предположим, что объекты с одинаковыми именами могут принадлежать различным источникам. Покажем, как с помощью ключей можно решить следующие задачи:
- найти объект с определенным именем и источником;
- найти объекты с определенным именем;
- найти объекты с определенным источником.
Листинг 8.27. Входящий документ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Для элементов item
мы будем генерировать ключи, значения которых будут состоять из двух частей — источника и имени, разделенных символом "-
". Для того чтобы решить одним ключом все три поставленные задачи, мы будем использовать для его определения три элемента xsl:key
.
Листинг 8.28. Входящий документ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Листинг 8.29. Выходящий документ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
У приведенного здесь способа формирования ключа есть определенные ограничения: необходимо иметь априорную информацию о строковых значениях каждого из свойств, составляющих наш композитный ключ для того, чтобы корректно формировать его строковые представления. Например, если бы в приведенном выше документе имена объектов и источников могли бы содержать символ "-
", было бы непонятно, к какому объекту относится составной ключ "a-b-c
": к объекту с источником a-b
и именем c
или к объекту с источником a
и именем b-c
. К счастью, в большинстве случаев такая информация имеется, и генерировать составные ключи не очень сложно.
Функция key в паттернах¶
Разбирая синтаксические правила построения паттернов, мы встретились с особой формой паттерна, в котором могла использоваться функция key
. Приведем еще раз эту продукцию:
1 2 |
|
Функция key(key-name, key-value)
в паттерне будет соответствовать узлам, значение ключа key-name
которых равняется или принадлежит объекту key-value
. Это позволяет использовать возможности ключей при проверке узлов на соответствие образцу.
Пример 9
Предположим, что нам нужно по-особому обработать объекты, принадлежащие источнику a
. Для этого мы можем создать шаблон следующего вида.
Листинг 8.30. Шаблон, использующий функцию key в паттерне
1 2 3 |
|
Этот шаблон будет применяться к любым узлам, имеющим ключ src
со значением a
.