Работаем с 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-семантику.

Примечания читателей

Valery A. Ilychev (sarutobi at pisem.net):

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


-->
Не совсем верное утверждение относительно основного использования. XSLT
предназначен для быстрого преобразования произвольных документов в формате XML 
в XML документ с требуемой структурой.
И еще. Поскольку достаточно большое число форматов представляет из себя
обычный текст с разметкой (RTF, TeX, HTML, и т.п.), то можно сказать,
что с помощью XSLT можно получить выходной документ, пригодный для
обработки в большинстве имеющихся приложений, 
-->  


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

-->
составные части XSL:
XSLT - расширяемый язык разметки. Отвечает за преобразование входного
документа.
XPath - язык, предназначенный для обращения к частям XML-документа,
проводить выборки и основные вычисления.
Что же касается XSL Formatting Objects - возможно, это только
рекомендация w3.org, так как в описании стандарта 1.0 мне эта часть не
попалась.
-->
 
> Прежде всего следует указать, что наш документ использует стилистику XML и
> импортировать пространство имен XML:
>
>     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
>...
>

-->
Также в этом месте можно включить некоторые дополнительные возможности,
предоставляемые XSLT - определить тип выходного документа и
необходимость вывода XML-заголовка:


method может быть одним из трех вариантов - text, xml и html. Возможно,
в будущем w3.org добавит другие типы. Этот параметр влияет на способ
формирования выходного документа (например, нужно ли делать замены 
"<" на "<")
omit-xml-declaration определяет, будет ли в начале каждого
результирующего документа выводиться строка
 ("no") или эту строку выводить не
нужно("yes").
-->

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

-->
XSLT предоставляет два способа определения правила шаблонного
преобразования:
xsl:template match и xsl:template name
Их можно назвать "условным" и "безусловным" определением шаблона. Первый
из них срабатывает, если название обрабатываемого элемента совпадает с
выражением, указанным в 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="/" является аналогом функции main() в C/C++ и
применяется практически всегда. разбор документа начинаестя именно с
этого правила.
-->

> Внутри элемента xsl:template match следует указать вложенные узлы
> элементом: xsl:value-of select.

-->
Некорректное утверждение. Внутри элемента xsl:template можно указать
почти все допустимые с точки зрения xsl узлы. В данном случае автор
использует xsl:value-of select, возвращающее содержание узла, указанного в
select
-->

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

-->  
В данном случае лучше было бы определить переменную, а не дважды
вычислять одно и то же выражение XPath
-->

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

-->
Безо всяких переходов начали объяснять XPath.
С помощью этого языка возможно обращение к любому элементу в документе. в данном случае
применяется обращение к атрибуту. 
-->


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

-->
переменная в XSLT является именно полноправной переменой, а не
константой.:)) Невозможность же изменения переменной заложена в самой
технологии XSLT. Это связано с тем, что XSLT является _декларативным_
языком программирования (то есть нужно указать только входные данные и 
требуемый вид результата. О том, _как_ это сделать, заботится уже не 
программист, а процессор). То есть преобразование документа - это
применение набора шаблонов, а не последовательность действий.
-->


> Инструкция if
>
> Иногда возникает необходимость поместить в выходной документ некоторый
> текст, если задан какой-либо xml-элемент (или его атрибут), либо другой
> текст, если этот элемент (или атрибут) отсутствует. В таких случаях можно
> использовать элемент xsl:if.

-->
Одним из недостатков элемента xsl:if является его усеченность.
Конструкция if-then-else в XSLT отсутствует. Её приходится эмулировать с
помощью xsl:choose
-->

>  <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>


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

-->
Это один из вариантов. Также для решения этой задачи можно использовать сущности
< и >, которыми заменяются символы "<" и ">", или определить свои
собственные сущности.
-->

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

-->
Я пользуюсь процессором Sablotron. Этот процессор существует в виде
исполняемой библиотеки для многих платформ. Имеются утилиты для работы
из командной строки и API для подключения к языкам высокого уровня (C,
PERL, PHP, Python).

Для работы из командной строки используется утилита sabcmd.
чтобы увидеть результат преобразования на экране, нужно дать команду
sabcmd XSLT_file XML_document
а чтобы отправить результат в файл
sabcmd XSLT_file XML_document result_document

также поддерживаются режимы batch, применить несколько XSLT к одному
документу, применить одну XSLT к нескольким документам. Подробнее об
этих режимах смотрите в документации.
-->

Alex M. (shurick_a at mail.ru):

Хотелось бы высказать свое мнение по поводу этого:
> 
>    <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>
> 

Конструкций подобного рода следует избегать, и использовать лишь в 
крайних случаях. Почему?

Во-первых, это противоречит идеологии XML. Ведь XSLT-шаблон это, по 
сути, тот же XML документ, а в XML документе не может быть незакрытых тэгов.

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

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

Правильней в данном случае было бы воспользоваться элементом xsl:attribute

<table width="100%">
    <xsl:attribute name="bgcolor">
    <xsl:choose>
       <xsl:when test="$even = 1">#cccccc</xsl:when>
       <xsl:when test="$even = 0">#99b0bf</xsl:when>
       <xsl:otherwise>#ffffff</xsl:otherwise>
    </xsl:choose>
    </xsl:attribute>
..
..
..
</table>

Или переменной:

<xsl:variable name="color">
<xsl:choose>
    <xsl:when test="$even = 1">#cccccc</xsl:when>
    <xsl:when test="$even = 0">#99b0bf</xsl:when>
    <xsl:otherwise>#ffffff</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<table width="100%" bgcolor="{$color}">
..
..
..
</table>

Вообще, практика использования [CDATA[...]] в написании 
XSLT-преобразований сродни использованию оператора GOTO из Basic'а.  ;-)

Copyright (C) 2003, Daniel Guerrero. Copying license http://www.linuxgazette.com/copying.html
Published in Issue 89 of Linux Gazette, April 2003


Вернуться на главную страницу