Содержание


Экономия времени и сокращение кода с помощью XPath 2.0 и XSLT 2.0

Создание простых в обслуживании таблиц стилей с помощью оператора to, типа данных item и последовательностей

Comments

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

При работе с документом XML в XPath 2.0 и XSLT 2.0 роль древовидной структуры XPath 1.0 и XSLT 1.0 играет последовательность. Последовательность содержит один элемент (узел документа), и работа с ним проводится так же, как всегда. Однако вы можете создавать последовательности атомарных значений. Листинг 1, взятый из примера приложения для работы с данными турнира по олимпийской системе на 16 команд, который мы рассмотрим ниже, содержит пример последовательности атомарных значений.

Листинг 1. Последовательность атомарных значений
<xsl:variable name="seeds" as="xs:integer*">
  <xsl:sequence 
    select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>

В этом коде определяется переменная $seeds. Как можно догадаться, новый элемент <xsl:sequence> определяется последовательность элементов. В данном случае элементами являются xs:integer схемы XML. Новый атрибут as определяет тип данных переменной, а звёздочка (xs:integer*) обозначает, что последовательность содержит ноль или более целых чисел. В XPath 1.0 и XSLT 1.0 нужно было бы создавать 16 различных текстовых узлов и затем сгруппировать их в переменную. Последовательность в XPath 2.0 и XSLT 2.0 работает как одномерный массив целых чисел, и это именно то, что нам нужно для рассматриваемого примера.

При работе с последовательностями нужно соблюдать несколько правил. Во-первых, последовательности не могут включать в себя другие последовательности. Если создаётся новая последовательность из трёх элементов, за которой следует другая последовательность из трёх элементов, результатом будет новая последовательность из шести элементов. Во-вторых, последовательности позволяют смешивать узлы и элементы. Можно создать последовательность атомарных значений, показанную в листинге 1, и добавить к ней все элементы <contestant>, как показано в листинге 2.

Листинг 2. Последовательность узлов и атомарных значений
<xsl:variable name="seeds" as="item()*">
  <xsl:for-each select="/bracket/contestants/contestant">
    <xsl:copy-of select="."/>
  </xsl:for-each>
  <xsl:sequence 
    select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>

В переменной $seeds содержатся все узлы участников и 16 атомарных значений, использованных ранее. Обратите внимание, что теперь переменная имеет тип данных item()*. Тип item - это узел или атомарное значение, поэтому такая переменная может содержать всё что угодно.

Теперь, когда вы знакомы с последовательностями и типом item, давайте посмотрим на оператор to, введённый в XPath 2.0 и XSLT 2.0. Он позволяет вам выбрать диапазон целых чисел. Например, можно создать последовательность, подобную показанной в листинге 3.

Листинг 3. Последовательность целых чисел, созданная оператором to
<xsl:variable name="range" as="item()*">
  <xsl:sequence select="1 to 16"/>
</xsl:variable>

В этом коде создаётся переменная $range, содержащая целые числа от 1 до 16. В рассматриваемом примере приложения можно использовать оператор to для организации цикла, как показано в листинге 4.

Листинг 4. Использование оператора to для организации цикла
<xsl:for-each select="1 to 32">
  <!-- Do something useful here -->
</xsl:for-each>

Перед тем, как приступать к созданию таблицы стилей, давайте рассмотрим наш пример приложения подробнее.

Разбор примера приложения

Пример приложения, используемый для этой статьи, работает с данными турнира «навылет», в котором принимают участие 16 команд. Как можно было ожидать, турнирные данные представлены в формате XML. Мы создадим таблицу стилей XSLT 2.0, которая преобразует данные XML в таблицу HTML, показывающую результаты турнира. В листинге 5 показан формат этого документа XML.

Листинг 5. Документ XML с турнирными данными
<?xml version="1.0" encoding="UTF-8"?>
<!-- tourney.xml -->
<bracket>
   <title>RSDC Smackdown</title>
   <contestants>
      <contestant seed="1" image="images/homerSimpson.png">Donuts</contestant>
      <contestant seed="2" image="images/caffeine.png">Caffeine</contestant>
      <contestant seed="3" image="images/fearlessFreep.png">Fearless Freep</contestant>
      <contestant seed="4" image="images/wmd.jpg">Weapons of Mass Destruction</contestant>
      <contestant seed="5" image="images/haroldPie.jpg">Pie</contestant>
      <contestant seed="6" image="images/adamAnt.png">Adam Ant</contestant>
      <contestant seed="7" image="images/georgeWBush.jpg">Misunderestimated</contestant>
      <contestant seed="8" image="images/sillyPutty.jpg">Silly Putty</contestant>
      <contestant seed="9" image="images/krazyGlue.jpg">Krazy Glue</contestant>
      <contestant seed="10" image="images/snoopDogg.png">Biz-Implification</contestant>
      <contestant seed="11" image="images/atomAnt.png">Atom Ant</contestant>
      <contestant seed="12" image="images/ajaxcan.png">AJAX</contestant>
      <contestant seed="13" image="images/darthVader.jpg">Darth Vader</contestant>
      <contestant seed="14" image="images/nastyCanasta.png">Nasty Canasta</contestant>
      <contestant seed="15" image="images/jcp.png">Java Community Process</contestant>
      <contestant seed="16" image="images/andre.png">Andre the Giant</contestant>
   </contestants>
   <results>
      <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
      <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
      <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
      <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
      <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
      <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
      <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
      <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
      <result round="2" firstSeed="1" secondSeed="9" winnerSeed="1"/>
      <result round="2" firstSeed="5" secondSeed="4" winnerSeed="5"/>
      <result round="2" firstSeed="11" secondSeed="3" winnerSeed="3"/>
      <result round="2" firstSeed="10" secondSeed="2" winnerSeed="2"/>
      <result round="3" firstSeed="1" secondSeed="5" winnerSeed="1"/>
      <result round="3" firstSeed="3" secondSeed="2" winnerSeed="2"/>
      <result round="4" firstSeed="1" secondSeed="2" winnerSeed="2"/>
   </results>
</bracket>

В элементе <title> указывается название турнира - RSDC Smackdown. В этом документе представлены реальные результаты с секции конференции разработчиков IBM Rational Software Developer Conference, прошедшей в этом году.

В группе содержится 16 элементов <contestant>, у каждого из которых есть название (текст элемента), турнирный номер и изображение. Турнир 16 команд состоит из 15 встреч. Каждая встреча представлена элементом <result>. С каждой встречей связаны четыре элемента данных: раунд турнира, в котором прошла встреча (атрибут round), турнирные номера двух участников (хранятся в атрибутах firstSeed и secondSeed) и турнирного номера победителя (атрибут winnerSeed). Наша задача состоит в обработке документа XML и его преобразовании в таблицу HTML с результатами, как на рисунке 1.

Рисунок 1. Результат турнира в таблице HTML
Результат турнира в таблице HTML
Результат турнира в таблице HTML

В таблице 32 строки и пять столбцов. При использовании XSLT 1.0 можно построить таблицу HTML построчно, как показано в листинге 6.

Листинг 6. Построчное создание таблицы HTML
<!-- Row 1 -->
<tr>
  <td style="border: none; border-top: solid; border-right: solid;">
    <xsl:text>[1] </xsl:text>
    <xsl:value-of select="$contestants[@seed='1']/>
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
</tr>
<!-- Row 2 -->
. . .

Такая схема будет работать, но обслуживать эту таблицу стилей в будущем будет сложно. Код каждой строки и каждого столбца в таблице стилей повторяется множество раз. Основная проблема здесь в том, что в результирующей таблице должно быть 32 строки. В каждой из 32 строк содержатся данные элементов документа XML (либо <contestant>, либо <result>). К сожалению, у нас нет 32 элементов, по которым можно было бы организовать цикл. Можно использовать <xsl:for-each select="contestants/contestant|results/result">, но эти элементы выводятся не в том порядке, в котором они должны выводиться в таблицу. В XSLT 1.0 не так много инструментов, которые могут нам помочь.

Мы можем изменить код и сделать таблицу стилей значительно проще. В стиле и содержимом ячеек имеются закономерности, и по этим закономерностям можно организовать цикл с помощью новых функций XPath 2.0 и XSLT 2.0. Конечная версия таблицы стилей будет примерно на 70% короче оригинальной, надо сказать, весьма неуклюжей. Перед тем как приступить к созданию такой таблицы, давайте посмотрим, как изменить код.

Изменение кода - определение стилей ячеек таблицы

Для изменения кода начнём с перемещения информации о стилях в таблицу стилей CSS. Как видно из рисунка 2, в таблице HTML есть пять различных стилей ячеек: None, MatchupStart, MatchupMiddle, MatchupEnd и Solid.

Рисунок 2. Стили границ ячеек таблицы HTML
Стили границ ячеек таблицы HTML
Стили границ ячеек таблицы HTML

В листинге 7 показано, как будет выглядеть код CSS.

Листинг 7. Таблица стилей CSS
.None          { width: 20%; }
.MatchupStart  { width: 20%; 
                 border: none; border-top: solid; 
                 border-right: solid; }
.MatchupMiddle { width: 20%; 
                 border: none; border-right: solid; }
.MatchupEnd    { width: 20%; 
                 border: none; border-bottom: solid; 
                 border-right: solid; }
.Solid         { width: 20%; 
                 border: solid; }

Стили ячеек облегчают просмотр результатов турнира. Строки, объединяющие две ячейки, показывают, что эти два соперника встретились друг с другом; ячейка в следующем столбце, обведенная сплошной линией, означает победителя встречи. Посмотрев на стили границ, можно увидеть четкую закономерность: Для каждой пары соревнующихся справедливо следующее: ячейки перед первым участником не обведены (стиль None), ячейки для двух участников обведены сплошной линией (стиль Solid), у ячеек между двумя участниками есть обводка с правой стороны (стиль MatchupMiddle), а у ячеек после последнего участника обводки нет (стиль None). В первом столбце шаблон немного другой. Поскольку пары соревнующихся в первом столбце расположены близко друг к другу, у ячейки первого участника есть обводка сверху и справа (стиль MatchupStart), а у ячейки второго участника - снизу и справа (стиль MatchupEnd).

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

Общая закономерность по всей таблице - в каждом столбце содержится повторяющаяся группа ячеек. Размер повторяющихся групп (я буду называть их периодом столбца, за отсутствием лучшего термина) для пяти столбцов будет составлять 4, 8, 16, 32 и 64 соответственно. В каждой повторяющейся группе содержится два участника, ячейки между участниками, а также ячейки до и после двух участников.

Для выполнения расчётов в каждом столбце используются два значения: $period, представляющее величину повторяющейся группы, и $oneQuarter, равное одной четвертой величины периода. (Хранение $period div 4 в переменной делает код чище.) В таблице 1 показаны обобщённые правила стилей границ ячеек таблицы.

Таблица 1. Стили границ ячеек таблицы HTML
Формула Столбец 1 (период 4)Столбец 2 (период 8)Столбец 3 (период 16)Столбец 4 (период 32)Столбец 5 (период 64)
$row < $oneQuarter or $row > $period - $oneQuarterНетСтрока 1, стиль NoneСтроки 1-3, стиль NoneСтроки 1-7, стиль NoneСтроки 1-15, стиль None
$row = $oneQuarterСтрока 1, стиль MatchupStartСтрока 2, стиль SolidСтрока 4, стиль SolidСтрока 8, стиль SolidСтрока 16, стиль Solid
$row > $oneQuarter and $row < $period - $oneQuarterСтрока 2, стиль MatchupMiddleСтроки 3-5, стиль MatchupMiddleСтроки 5-11, стиль MatchupMiddleСтроки 9-23, стиль MatchupMiddleСтроки 17-32, стиль None
$row = $period - $oneQuarterСтрока 3, стиль MatchupEndСтрока 6, стиль SolidСтрока 12, стиль SolidСтрока 24, стиль SolidНет
$row < $oneQuarter or $row > $period - $oneQuarterСтрока 4, стиль NoneСтроки 7-8, стиль NoneСтроки 13-16, стиль NoneСтроки 25-32, стиль NoneНет

Теперь у нас есть изящный способ определения стиля границы заданной ячейки таблицы. Формула отлично работает для заданного номера столбца и строки.

Изменение кода - определение содержания ячеек таблицы

При создании ячейки таблицы нам также нужно добавить в неё соответствующее содержание. Здесь также имеется отличная закономерность. Ячейки со стилем None или MatchupMiddle будут пустыми. Это означает, что необходимо найти нужное содержимое для остальных трёх стилей.

Как можно увидеть из таблицы 1, у ячеек с содержимым есть свойство $row = $oneQuarter or $row = $period - $oneQuarter. В первом столбце просто записывается турнирный номер и название соответствующего участника. Встречи проходят по турнирным номерам, образующим пары, как показано в таблице 2.

Таблица 2. Соревнования по турнирным номерам
[1] против [16]
[8] против [9]
[5] против [12]
[4] против [13]
[6] против [11]
[3] против [14]
[7] против [10]
[2] против [15]

Встречи расставлены таким образом, чтобы в случае, если всегда будет выигрывать верхний номер, команда с самым высоким номером будет всегда играть с командой с самым низким номером, вторая команда сверху будет играть со второй командой снизу, и так далее. Если посмотреть на турнирные номера в таком порядке (1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15), можно увидеть, что они совпадают со значениями нашей последовательности $seeds. В этом порядке выводятся участники в столбце 1.

Участники выводятся через строку, то есть в первой строке выводится первый турнирный номер в последовательности, в третьей строке - второй, в пятой - третий номер последовательности и так далее. Закономерность здесь имеет вид $row + 1 div 2 = $index. Другими словами, для того, чтобы получить порядковый номер участника в последовательности $seeds, нужно взять номер строки, прибавить 1 и разделить результат на 2. В ячейке таблицы будут содержаться турнирный номер и имя соответствующего участника.

Мы закончили с содержимым для столбца 1. Столбцы со второго по пятый, как можно ожидать, будут более сложными. Теперь, вместо того, чтобы смотреть на элементы <contestant>, нам нужно использовать результаты, хранящиеся в 15 элементах <result>.

В строке 2 содержатся победители первого круга. Это означает, что нам нужны элементы <result> с атрибутом round="1". С целью упрощения процесса победитель первой встречи (1 против 16) хранится в первом элементе <result>, победитель второй встречи (8 против 9) хранится во втором элементе <result>, и так далее. Победители в столбце 2 выводятся в строках 2, 6, 10, 14, 18, 22, 26 и 30. Давайте определим закономерность; в строке 2 используется первый элемент <result>, в строке 6 - второй, в строке 10 - третий. В этом столбце для получения положения соответствующего элемента <result round="1"> нужно взять номер строки, прибавить 2 и разделить на 4.

Столбцы 3 и 4 обрабатываются аналогичным образом. Победители в столбце 3 выводятся в строках 4, 12, 20 и 28 (закономерность - прибавить 4 и разделить на 8). Победители в столбце 4 выводятся в строках 8 и 24 (прибавить 8 и разделить на 16). В пятом столбце присутствует всего один участник—победитель всего турнира. Победитель выводится в строке 16 из одного элемента <result round="4">.

Новый способ поиска положения искомого значения выглядит так: ($row + $oneQuarter) div ($oneQuarter * 2). Использование повторяющегося шаблона увеличивающегося размера упрощает код. Рассчитываемая позиция в столбце 1 является указателем последовательности отборочных номеров; рассчитываемое положение является положением соответствующего элемента <result>.

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

Использование возможностей XPath 2.0 и XSLT 2.0

Теперь для упрощения таблицы стилей мы можем использовать новые технологии, реализованные в XPath 2.0 и XSLT 2.0. Для начала используем оператор to . Если бы мы строили таблицу на процедурном языке программирования, код мог бы быть похож на приведенный в листинге 8.

Листинг 8. Процедурный подход к таблице стилей
      for (int row=1; row<=32; row++)
        for (int column=1; column<=5; column++)
          // Build each cell in the table here

Для замены цикла for процедурного языка в XPath 2.0 и XSLT 2.0 используется <xsl:for-each>. В листинге 9 показано, как это делается.

Листинг 9. Организация циклов for с использованием оператора to
<xsl:for-each select="1 to 32">
  <xsl:variable name="outerIndex" select="."/>
    <tr>
      <xsl:for-each select="1 to 5">

Здесь нужно преодолеть несколько сложных моментов. Во-первых, контекст каждой итерации в XPath 1.0 и XSLT 1.0 изменялся из-за <xsl:for-each>. Например, если используется <xsl:for-each select="contestants/contestant>, во время каждой итерации узлом контекста будет последний <contestant>. Поскольку для организации цикла по различным целым числам используется оператор to, элемент контекста (item контекста в 2.0) не определен. Как можно увидеть из листинга 9, необходимо сохранить текущее значение внешнего <xsl:for-each>, поскольку он недоступен во внутреннем <xsl:for-each>.

Но на самом деле всё гораздо хуже. Если контекст элемента не определён, вы не можете выбрать узлы документа. Если вы находитесь в первой строке и в первом столбце, можно получить первый элемент последовательности $seeds, поскольку $seeds является глобальной переменной. Она говорит вам, что нужно найти элемент <contestant seed="1">. К сожалению, мы не можем ничего получить из нашего документа. Не помогает даже абсолютное выражение XPath, например, /bracket/contestants/contestant[@seed='1']. Поэтому нужно хранить узлы, с которыми придётся работать, в глобальных переменных. В листинге 10 показано, как обращаться к глобальным переменным, когда вам это нужно.

Листинг 10. Глобальные переменные, в которых хранятся нужные узлы
<xsl:variable name="results" select="/bracket/results"/>

<xsl:variable name="contestants" select="/bracket/contestants"/>

Выражение XPath для получения названия участника с турнирным номером 16 будет иметь вид $contestants/contestant[@seed="16"]. Доступ к элементам <result> выполняется подобным же образом; если нужно получить победителя второй встречи во втором круге, выражение будет иметь вид $results/result[@round="2"][2]/@winnerSeed. В листинге 11 показаны ещё две глобальные переменные, которые можно использовать для создания таблицы HTML.

Листинг 11. Другие полезные глобальные переменные
<xsl:variable name="periods" as="xs:integer*">
  <xsl:sequence select="(4, 8, 16, 32, 64)"/>
</xsl:variable>

<xsl:variable name="backgroundColors" as="xs:string*">
  <xsl:sequence 
    select="('background: #CCCCCC;', 'background: #9999CC;',
             'background: #99CCCC;', 'background: #CC99CC;',
             'background: #CCCC99;')"/>
</xsl:variable>

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

Ещё одним приятным усовершенствованием, введенным в XPath 2.0 и XSLT 2.0, является элемент <xsl:function>. Давайте создадим в таблице стилей две функции: cellStyle и getResults. Первая функция возвращает стиль границы для ячейки, а вторая - результат для заданной встречи, если таковой есть. Параметрами обеих функций являются номер строки и столбца ячейки. В листинге 12 показан код функции cellStyle.

Листинг 12. Функция cellStyle
<xsl:function name="bracket:cellStyle" as="xs:string">
  <xsl:param name="row" as="xs:integer"/>
  <xsl:param name="column" as="xs:integer"/>
  
  <xsl:variable name="period" as="xs:integer"
    select="subsequence($periods, $column, 1)"/>
  <xsl:variable name="oneQuarter" as="xs:integer"
    select="$period div 4"/>
  <xsl:variable name="lastColumn" as="xs:boolean"
    select="$oneQuarter = count($contestants/contestant)"/>
  <xsl:variable name="position" select="$row mod $period"/>
  
  <xsl:value-of 
    select="if ($position = $oneQuarter) then
                (if ($column = 1) then 'MatchupStart'
                    else 'Solid')
            else if ($position = $period - $oneQuarter) then
                     (if ($column = 1) then 'MatchupEnd'
                         else 'Solid')
            else if ($lastColumn) then 'None'
            else if ($position &lt; $oneQuarter or 
                     $position &gt; $period - $oneQuarter) then 'None'
            else 'MatchupMiddle'"/>
</xsl:function>

Для того, чтобы определить стиль ячейки, нужно использовать четыре переменные. Значение $period извлекается из глобальной переменной $periods. Как я уже упоминал выше, $oneQuarter - это просто $period div 4. Булево значение $lastColumn просто сравнивает $oneQuarter со счетчиком участников турнира. Если значения равны, значит, производится обработка последнего столбца. И, наконец, переменная $position показывает положение текущего столбца в шаблоне. Другими словами, строка 9 таблицы - это первая строка повторяющейся группы для столбцов 1 и 2. Для определения стиля ячейки используется положение строки в шаблоне.

В XPath 2.0 и XSLT 2.0 реализован оператор if, в котором указывается выражение в скобках, за которым следует then и else. Всё это переходит в атрибут select элемента <xsl:value-of>. В этом примере мы заменяем значительно более пространный элемент <xsl:choose> одним выражением. Учитывая рассмотренные в приведённой выше таблице формулы, код будет очень простым; если бы логика была более сложной, возможно, было бы удобнее использовать <xsl:choose>, даже если для этого потребуется больше определений типов.

Замечания по синтаксису <xsl:function>: Во-первых, нужно определить новое пространство имен для функций. Если в выражении XPath вызывается bracket:cellStyle(), пространство имён скажет процессору XSLT 2.0, как найти функцию. Во-вторых, обратите внимание, что для обозначения того, что функция возвращает строковое значение, используется атрибут as="xs:string". Элемент <xsl:value-of> возвращает одно из пяти названий стилей; он является результатом работы функции.

Функция getResults чуть более сложная, её код представлен в листинге 13.

Листинг 13. Функция getResults
<xsl:function name="bracket:getResults" as="xs:string">
  <xsl:param name="row" as="xs:integer"/>
  <xsl:param name="column" as="xs:integer"/>
  
  <xsl:variable name="period" as="xs:integer"
    select="subsequence($periods, $column, 1)"/>
  <xsl:variable name="oneQuarter" as="xs:integer"
    select="$period div 4"/>
  <xsl:variable name="position" select="$row mod $period"/>
  
  <xsl:choose>
    <xsl:when test="$position = $oneQuarter
                    or
                    $position = $period - $oneQuarter">
      <xsl:variable name="round" select="$column - 1"/>
      <xsl:variable name="index" 
        select="($row + $oneQuarter) div ($oneQuarter * 2)"/>
      <xsl:variable name="currentSeed"
        select="if ($column = 1) then 
                  subsequence($seeds, $index, 1)
                else
                  $results/result[@round=$round][$index]/@winnerSeed"/>
      <xsl:choose>
        <xsl:when test="string-length(string($currentSeed))">
          <xsl:value-of 
            select="concat('[', $currentSeed, '] ', 
                    $contestants/contestant[@seed=$currentSeed])"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:text>&#160;</xsl:text>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text>&#160;</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>

Параметры остаются прежними. Необходимо организовать особую обработку первого столбца, в котором содержатся данные из элементов <contestant>, тогда как данные для других столбцов берутся из элементов <result>. Так же, как и в случае функции cellStyle, мы определяем $period и $position, и используем переменную $oneQuarter для упрощения формул.

Если в ячейке содержится участник, выполняем расчет ещё трёх переменных. Переменная $round на единицу меньше номера столбца (во втором столбце содержатся результаты первого круга), рассчитываем переменную $index по описанной ранее формуле.

Для поиска соответствующих данных нужно выполнить два действия. Во-первых, нужно установить значение переменной $currentSeed. Если речь идёт о первом круге, то для получения значения переменной $seeds используется функция subsequence. Для других кругов атрибут winnerSeed получается из соответствующего элемента <result>.

Во-вторых, в дополнение к отдельной обработке первого столбца, необходимо учесть возможность наличия элемента <result>, в котором не будет победителя (winnerSeed=""). Если такое произойдёт, нужно вернуть неразрывный пробел (&#160;). Из-за способа обработки типов данных в XSLT 2.0 (этому вопросу будет посвящена следующая статья) нам необходимо преобразовать $currentSeed в строку и проверить длину этой строки. В случае, если это строка нулевой длины, возвращаем неразрывный пробел, в противном случае возвращаем турнирный номер и название участника.

И, наконец, обратите внимание на то, что здесь мы используем <xsl:choose>. Хотя всё можно сделать с помощью одного оператора XPath if , код при этом будет очень громоздким. Несмотря на то, что <xsl:choose> достаточно многословен, код становится чище и проще для понимания.

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

Листинг 14. Основа таблицы стилей
<xsl:for-each select="1 to 32">
  <xsl:variable name="outerIndex" select="."/>
  <tr>
    <xsl:for-each select="1 to 5">
      <td style="{subsequence($backgroundColors, ., 1)}"
          class="{bracket:cellStyle($outerIndex, .)}">
        <xsl:value-of 
          select="bracket:getResults($outerIndex, .)"/>
      </td>
    </xsl:for-each>
  </tr>
</xsl:for-each>

У нас получилось два цикла, создающие таблицу из пяти столбцов и 32 строк. Цвет фона каждой ячейки определяется на основе текущего номера столбца. Функция cellStyle определяет стиль границы (class="x"), а функция getResults - значение ячейки.

Использование таблицы стилей

Как можно догадаться, для обработки этой таблицы стилей необходим процессор XSLT 2.0. Я не хочу повторять здесь всю таблицу стилей, но для того, чтобы используемый вами процессор перешёл в режим XSLT 2.0, необходимо использовать директиву <xsl:stylesheet version="2.0">. Я очень рекомендую использовать процессор Saxon Майкла Кея (ссылку на дополнительную информацию можно найти в разделе Ресурсы). Доктор Кей выступал редактором спецификации XSLT 2.0, и во время её разработки Saxon в некоторых случаях использовался в качестве контрольного примера. Если вы работаете с Saxon, то для преобразования файла XML tourney.xml с помощью таблицы стилей results-html.xsl и записи результата в файл results.html используйте команду:

java net.sf.saxon.Transform -o results.html tourney.xml results-html.xsl

Для того чтобы проиллюстрировать гибкость таблицы стилей, уберём некоторые из результатов из файла XML и запустим преобразование. В листинге 15 показано, как будут выглядеть элементы <result>.

Листинг 15. Документ XML с неполными данными
<?xml version="1.0" encoding="UTF-8"?>
<!-- incomplete-tourney.xml -->
<bracket>
. . .
   <results>
      <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
      <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
      <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
      <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
      <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
      <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
      <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
      <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="4" firstSeed="" secondSeed="" winnerSeed=""/>
   </results>
</bracket>

В листинге 15 показано состояние турнира после первого круга. У всех элементов <result> для 2, 3 и 4 кругов атрибуты winnerSeed пусты. Несмотря на это, таблица стилей формирует правильную таблицу, показанную на рисунке 3.

Рисунок 3. Таблица для неполного турнира
Неполная таблица
Неполная таблица

Резюме

В этой статье было показано много новых возможностей XPath 2.0 и XSLT 2.0. В созданном нами примере приложения мы взяли громоздкую таблицу стилей и преобразовали её в значительно меньший и более удобный в работе код. Часть наших усилий пошла на анализ кода и поиск закономерностей, но без оператора to, <xsl:function> и последовательностей элементов упростить таблицу стилей было бы значительно сложнее.

Самое главное, мы создали общие функции, которые могут работать с любым числом участников. Для того чтобы адаптировать таблицу стилей, например, для турнира 32 команд, достаточно изменить последовательности $seeds и $periods. Также нужно заменить select="1 to 5" на select="1 to $rounds", где $rounds представляет количество кругов в турнире. Наиболее изящным решением, конечно же, было бы создание функций XSLT 2.0, которые рассчитывали бы значения для произвольной таблицы, в том числе последовательностей отборочных номеров (в сетке на 32 команды проходят матчи между 1 и 32 командой, 2 и 31 и так далее), а также периодов для различных столбцов. Оставим это упражнение для читателя.

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


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи Save time and code with XPath 2.0 and XSLT 2.0 (EN).
  • Что такое язык XSLT? (EN) (Майкл Кей, developerWorks, апрель 2005 г.): эта статья познакомит вас с языком XSLT, лежащей в его основе философией, его истоками, его преимуществами и наилучшими способами применения.
  • Планирование обновления XSLT 1.0 до 2.0 (EN) (Дэвид Мартсон, Джоанн Тонг и Генри Зонгаро; developerWorks, июль 2007 г.): из этой серии можно узнать, как перевести таблицы стилей XSLT 1.0 в новый формат.(EN)
  • Страница семейства расширяемых языков таблиц стилей: представлены новые рекомендации для XSLT 2.0, XPath 2.0, XQuery 1.0 и других спецификаций, которые с 23 января 2007 г. стали официальными рекомендациями консорциума W3C. Полное определение языка распределено по нескольким спецификациям.(EN)
  • Процессор XSLT 2.0 Saxon: загрузите процессор Майкла Кея с сайта SourceForge.(EN)
  • Страница Altova XML на Web-сайте компании Altova : познакомьтесь с XSLT-процессором Altova и попробуйте его в работе. Компания Altova, создатель XMLSpy и других популярных продуктов, сделала свой процессор XSLT бесплатным. Он поддерживает XSLT 1.0, XSLT 2.0 и XQuery 1.0. Несмотря на то, что его исходный код закрыт, лицензия позволяет вам встраивать этот механизм XSLT в собственные продукты.(EN)
  • Сертификация XML корпорации IBM : узнайте, как стать сертифицированным разработчиком IBM в области XML и связанных с ним технологий. (EN)
  • Ознакомительные версии программного обеспечения IBM : используйте в своем следующем проекте ознакомительные версии программных продуктов, которые можно загрузить непосредственно с сайта developerWorks.(EN)

Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML
ArticleID=295880
ArticleTitle=Экономия времени и сокращение кода с помощью XPath 2.0 и XSLT 2.0
publish-date=03202008