Работаем с XSLT.
Автор: Daniel Guerrero
Перевод: Андрей Киселев


XSLT (от англ. eXtensible Stylesheet Language for Transformations -- Расширяемый Язык Стилей для Преобразований) используется, по большей части, для преобразования данных из формата XML в формат HTML. Однако, XSLT может использоваться для преобразования из XML (или любого другого формата, использующего пространство имен xml, подобно RDF) в любой другой формат, даже в простой текст.

Консорциум W3 определяет три составные части языка XSL (от англ. eXtensible Stylesheet Language -- Расширяемый Язык Стилей): XSLT, XPath (язык путей и выражений, используемый в XSLT для доступа к отдельным частям XML-документа) и XSL Formatting Objects -- словарь, определяющий семантику форматирования документов.

Встречаем XSLT

Прежде всего следует указать, что наш документ использует стилистику XML и импортировать пространство имен XML:

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

 ...

</xsl:stylesheet>

Далее, основным элементом, который мы будем использовать, является xsl:template match. Этот элемент вызывается всякий раз, когда имя xml-узла совпадает со значением атрибута xsl:template match:

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

<xsl:template match="/"> <!-- оператор '/' взят из XPath и ассоциируется с корневым элементом -->
    <!-- выполнить какие либо действия с вложенными узлами -->
</xsl:template>

</xsl:stylesheet>

Внутри элемента xsl:template match следует указать вложенные узлы элементом: xsl:value-of select. Давайте для начала создадим xml-документ, содержащий некоторую информацию:

<!-- hello.xml -->

<hello>
   <text>Hello World!</text>
</hello>

Так должно выглядеть xslt-преобразование, которое вынимает узел text из корневого элемента (hello):

<!-- hello.xsl -->
<xsl:stylesheet version="1.0"
                  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
    <head>
      <title> <xsl:value-of select="//text"/> </title>
       <!--  в данном случае '//text' это: 'hello/text', но, поскольку я ленив сам по себе, я делаю это проще, используя выражение XPath  -->
    </head>

    <body>
       <p>
           Содержимое узла <b>text</b> корневого элемента: <b><xsl:value-of select="//text"/></b>
       </p>
    </body>
  </html>
</xsl:template>

</xsl:stylesheet>

В результате получится следующий HTML-документ:

<!-- hello.html -->

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <title>Hello World! </title>
   </head>
   <body>
      <p>
         Содержимое узла <b>text</b> корневого элемента: <b>Hello World!</b>
      </p>
   </body>
</html>

Получение значений атрибутов

конструкция @att возвращает значение атрибута att. Например:

<!-- hello_style.xml -->

<hello>
   <text color="red">Hello World!</text>
</hello>

XSLT-преобразование:

<!-- hello_style.xsl -->
<xsl:stylesheet version="1.0"
                  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
    <head>
      <title> <xsl:value-of select="//text"/> </title>
    </head>

    <body>
       <p>
           Содержимое узла <b>text</b> корневого элемента: <b><xsl:value-of select="//text"/></b>
           и его атрибут <b>color</b> : <xsl:value-of select="//text/@color"/>
       </p>
    </body>
  </html>
</xsl:template>

</xsl:stylesheet>

Результирующий HTML-документ:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <title>Hello World! </title>
   </head>
   <body>
      <p>
         Содержимое узла <b>text</b> корневого элемента: <b>Hello World!</b>
         и его атрибут <b>color</b> : red
      </p>
   </body>
</html>

Если вы задумаете использовать атрибут color для вывода текста Hello World! соответствующим цветом, то сделать это можно двумя способами: создать переменную и использовать ее для задания цвета шрифта или воспользоваться элементом xsl:attribute.

Переменные

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

(Странно, почему перменные названы переменными, а не константами. Прим.ред.)

Определяются переменные просто:

<!-- variables.xsl -->

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


<xsl:template match="/">

<!--  definition of the variable  -->
<xsl:variable name="path">http://somedomain/tmp/xslt</xsl:variable>

  <html>
    <head>
      <title>Пример с переменными</title>
  </head>

    <body>
       <p>
           <a href="{$path}/photo.jpg">Фотография моего последнего путешествия</a>
       </p>
    </body>
  </html>
</xsl:template>

</xsl:stylesheet>

Результирующий HTML-документ:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <title>Пример с переменными</title>
   </head>
   <body>
      <p><a href="http://somedomain/xslt/photo.jpg">Фотография моего последнего путешествия</a></p>
   </body>
</html>

Переменной можно присвоить значение узла или значение атрибута узла:

<!-- variables_select.xsl -->

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


<xsl:template match="/">
    <html>
       <head>
         <title>Пример с переменными</title>
        </head>
       <body>
           <xsl:apply-templates select="//photo"/>
       </body>
    </html>
</xsl:template>

<xsl:template match="photo">
    <!--  определение переменных  -->
    <xsl:variable name="path">http://somedomain/tmp/xslt</xsl:variable>
    <xsl:variable name="photo" select="file"/>
     <p>
       <a href="{$path}/{$photo}"><xsl:value-of select="description"/></a>
     </p>
</xsl:template>

</xsl:stylesheet>

Исходный xml-документ (я не стал сопровождать статью своими фотографиями, чтобы не напугать вас :-) )

<!-- variables_select.xml -->

<album>
   <photo>
      <file>mountains.jpg</file>
      <description>я - в горах</description>
   </photo>

   <photo>
      <file>congress.jpg</file>
      <description>я - на конгрессе</description>
   </photo>

    <photo>
      <file>school.jpg</file>
      <description>я - в школе</description>
   </photo>
</album>

Результирующий HTML-документ:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <title>Пример с переменными</title>
   </head>
   <body>
      <p><a href="http://somedomain/tmp/xslt/mountains.jpg">я - в горах</a></p>
      <p><a href="http://somedomain/tmp/xslt/congress.jpg">я - на конгрессе</a></p>
      <p><a href="http://somedomain/tmp/xslt/school.jpg">я - в школе</a></p>
   </body>
</html>

Как вы могли заметить, элемент <xsl:template match="photo"> был вызван трижды, это произошло потому, что всякий раз, когда xslt обнаруживает элемент, совпадающий с xsl:apply-templates, он вызывает соответствующий xsl:template match.

Итак! Вам не терпится вывести текст красным цветом? Попробуйте сделать это с помощью переменных, если не получится, то можете заглянуть в пример hello_style_variables.xsl

Сортировка

Сортировка XML-тегов в XSLT выполняется посредством элемента <xsl:sort select="sort_by_this_attribute"> Этот элемент должен размещаться внутри xsl:apply-templates (сортировка может производиться так же и в элементе xsl:for-each прим. перев.). Сортировка может выполняться как по самим xml-тегам, так и по их атрибутам, порядок сортировки можно задавать по возрастанию или по убыванию (если символы нижнего регистра должны предшествовать символам верхнего регистра или наоборот).

Для демонстрации сортировки я использовал пример альбома с фотографиями, в который добавил элемент <xsl:sort>:

 <xsl:apply-templates select="//photo">
        <xsl:sort select="file" order="descending">
 </xsl:apply-templates>

Здесь изменен порядок следования фотографий в выходном html-документе. Теперь xslt сначала упорядочит все элементы photo из xml-файла, а затем передаст их элементу template-match, вот почему xsl:sort должен находиться внутри элемента xsl:apply-templates.

Файлы xsl и html примера вы можете взять здесь:

Инструкция if

Иногда возникает необходимость поместить в выходной документ некоторый текст, если задан какой-либо xml-элемент (или его атрибут), либо другой текст, если этот элемент (или атрибут) отсутствует. В таких случаях можно использовать элемент xsl:if. Я продемонстрирую вам как это делается (этот пример взят из моих наработок в проекте TLDP-ES). Если вам известно, что некий исходный документ был преобразован в формат PDF, PS или HTML, то это обстоятельство можно отразить в xml-файле, т.е. если был создан PDF-файл, то в выходной html-файл вставляется ссылка на него:

     <xsl:if test="format/@pdf = 'yes'">
           <a href="{$doc_path}/{$doc_subpath}/{$doc_subpath}.pdf">PDF</a>
         </xsl:if>

Если атрибуту pdf документа присвоено значение "yes", как показано в примере:

   <document>
     <title>Bellatrix Library and Semantic Web</title>
     <author>Daniel Guerrero</author>
         <module>bellatrix</module>
         <format pdf="yes" ps="yes" html="yes"/>
   </document>

То в выходной html-файл будет вставлена ссылка на документ в PDF-формате. Если атрибуту присвоено значение "no" или любое другое, допустимое вашим преобразованием, значение, то ссылка не будет вставлена. Все вышесказанное вы можете увидеть в xsl и xml документах:

Инструкция for-each

Если вы внимательно посмотрите на xml-документ, приведенный выше, то заметите, что авторы представлены в виде списка имен, разделенных запятыми. Очевидно, что наилучшим выходом было бы поместить имена авторов в отдельные теги <author>:

   <document>
     <title>Donantonio: bibliographic system for automatic distribuited publication. Specifications of Software Requeriments</title>
         <author>Ismael Olea</author>
         <author>Juan Jose Amor</author>
         <author>David Escorial</author>
         <module>donantonio</module>
         <format pdf="yes" ps="no" html="yes"/>
   </document>

И вывести каждое имя в отдельной строке с помощью xsl:apply-templates и xsl:template match, но то же самое можно сделать и с помощью инструкции xsl:for-each.

     <xsl:for-each select="author">
            <tr>
               <td>
                      Author: <xsl:apply-templates />
               </td>
            </tr>
          </xsl:for-each>

В этом случае XSLT-процессор пройдет по списку авторов документа и, если вас интересует какой шаблон я использовал для обработки тегов <author>, я могу сказать - никакой. XSLT-процессор воспримет элемент apply-templates как обычный 'print' и выведет содержимое тега, выбранного элементом for-each.

Инструкция choose

Последний xslt-элемент, который я хочу вам продемонстрировать, это элемент choose. Он очень похож на инструкцию switch языка программирования C.

Первым должен идти элемент xsl:choose, а за ним дополнительные (один или несколько) элементы xsl:when, если требуется обрабатывать значение не подпадающее ни под одно из условий имеющихся элементов xsl:when, то вы можете добавить элемент xsl:otherwise:

  <xsl:variable name="even" select="position() mod 2"/>

  <xsl:choose>
     <xsl:when test="$even = 1">
       <![CDATA[<table width="100%" bgcolor="#cccccc">]]>
     </xsl:when>
     <xsl:when test="$even = 0">
        <![CDATA[<table width="100%" bgcolor="#99b0bf">]]>
     </xsl:when>
     <xsl:otherwise>
        <![CDATA[<table width="100%" bgcolor="#ffffff">]]>
     </xsl:otherwise>
  </xsl:choose>

Функция position() возвращает порядковый номер обрабатываемого элемента, в нашем случае -- документа. В данном примере нас интересует только четность порядкового номера, тем самым мы получаем возможность выделять четные и нечетные строки таблицы различным цветом. Я поместил элемент xsl:otherwise исключительно в демонстрационных целях, фактически же вы никогда не увидите строку с белым фоном в нашей таблице.

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

Я привел лишь короткий отрывок из примера, полный текст файлов xsl и html вы найдете по ссылкам:

Процессоры XSLT

Saxon

Процессор Saxon написан на языке Java, я пользуюсь версией 6.5.2. Все нижеследующие инструкции касаются этой версии, если у вас другая версия, то вам следует обратиться к документации для вашей версии за получением информации по установке и запуску процессора.

Установка

После того как вы скачаете архив с процессором saxon вам нужно распаковать его:

[danguer@perseo xslt]$ unzip saxon6_5_2.zip

Затем, вам нужно добавить файл saxon.jar к пути поиска классов, путь к jar-архиву можно передать с помощью ключа -cp path (можно добавить путь к jar-файлу в переменную окружения CLASSPATH прим. перев.). Я поместил файл saxon.jar в каталог xslt, кроме того необходимо передать Java используемый класс, в случае Saxon 6.5.2 используется класс com.icl.saxon.StyleSheet и затем должны следовать xml-документ и xsl-файл, например:

[danguer@perseo xslt]$ java -cp saxon.jar com.icl.saxon.StyleSheet document.xml tranformation.xsl

Эта команда отправит результат работы процессора на устройство стандартного вывода (STDOUT), перенаправить вывод в файл можно так:

[danguer@perseo xslt]$ java -cp saxon.jar com.icl.saxon.StyleSheet document.xml tranformation.xsl > file_processed.html

Например мы можем преобразовать наш первый пример XSLT с помощью процессора saxon:

[danguer@perseo xslt]$ java -cp saxon.jar com.icl.saxon.StyleSheet hello.xml hello.xsl > hello.html

xsltproc

Процессор xsltproc включен в состав большинства дистрибутивов, синтаксис вызова похож на вызов процессора saxon:

[danguer@perseo xslt]$ xsltproc hello.xsl hello.xml > hello.html

Я знаю о существовании и других процессоров, таких как sablotron, но я ими не пользовался, а потому не могу рекомендовать их вам ;-).

Ссылки


Daniel Guerrero

Я заканчиваю обучение на степень бакалавра BUAP в городе Пуэбло (Puebla), Мексика. Учавствую в работе проекта TLPD-ES (испанский вариант The Linux Documentation Project прим. перев.) все мои познания об этих технологиях я приобрел здесь. В настоящий момент я изучаю Web-семантику.

Copyright (C) 2003, Daniel Guerrero.

Hosted by uCoz