Изменение структуры данных XML с помощью XSLT 2.0

Содержание

Как вам уже известно, в работе с XML участвуют две стороны – формирующая XML-данные и использующая их сторона. Работа с XSL обычно ведется на стороне пользователя и очень часто формат данных, обрабатываемых шаблоном, является жестко фиксированым. Возьмите, к примеру, список книг в библиотечном каталоге. Вам может потребоваться XML-файл, содержащий список, отсортированный по названиям книг. А что если вам необходим список, отсортированный по авторам, или же вам потребуется вывести отдельный список авторов? Можно ли это сделать при помощи XSL?

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

В Листинге 1 представлен пример списка книг.

Листинг 1. Исходный XML-файл

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book name="Programming Ruby" author="Dave Thomas"/>
    <book name="Code Generation in Action" author="Jack Herrington"/>
    <book name="Pragmatic Programmer" author="Dave Thomas"/>
</books>

Здесь представлены три книги: Programming Ruby и Pragmatic Programmer автора Dave Thomas, а также книга Code Generation in Action, автором которой является ваш покорный слуга. Это все исходные данные, хотя в действительности нам необходимы лишь имена авторов – без повторений, по одной записи на каждого автора. В данном случае нам необходимо указать автора Dave Thomas только один раз.

Создание в памяти дополнительного дерева

Начнем с создания в памяти дерева для новой таблицы списка авторов (см. Листинг 2).

Листинг 2. Код для создания в памяти дерева списка авторов

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:template match="/">
        <xsl:variable name="allauthors">
            <authors>
                <xsl:for-each select="/books/book">
                    <author id="{@author}" />
                </xsl:for-each>
            </authors>
        </xsl:variable>
        <xsl:copy-of select="$allauthors" />
    </xsl:template>
</xsl:stylesheet>

Обратите внимание, что здесь указана версия шаблона XSLT 2.0. Это означает, что выполнить данное преобразование можно только при помощи парсера XSLT 2.0. В предыдущих версиях XSLT сохранение деревьев в памяти было возможно лишь с использованием дополнительного расширения. Теперь оно входит в стандартную реализацию, однако в объявлении XSLT-стиля необходимо обязательно указать номер версии – 2.0.

Вначале создадим переменную $allauthors при помощи тега xsl:variable. Все, что находится внутри этого тега, будет новым деревом, к которому потом можно будет обращаться через эту переменную. В нашем случае мы задаем корневой тег authors. Затем внутри него для каждой книги создаем новый тег author, задавая для него атрибут id. В Листинге 3 представлен результат XSLT-преобразования.

Листинг 3. Результат XSLT-преобразования

<?xml version="1.0" encoding="UTF-8"?>
<authors>
    <author id="Dave Thomas"/>
    <author id="Jack Herrington"/>
    <author id="Dave Thomas"/>
</authors>

Итак, мы видим содержимое переменной $allauthors, так как в конце шаблона используется тег xsl:copy-of (см. Листинг 2). Он очень удобен при работе с деревьями, сохраненными в памяти. Всякий раз, когда вам потребуется вывести содержимое переменной, достаточно использовать тег xsl:copy-of. Если вам не нужно выводить это содержимое, просто закомментируйте xsl:copy-of при помощи тега xsl:message (при этом отладочная информация выводится в стандартные логи с ошибками (stderr)).

Отладку деревьев, сохраняемые в памяти, можно также осуществлять при помощи отладчика XSLT, встроенного в XSLT/XML-редактор.

Сортировка по авторам

Следующим шагом создания отдельного списка авторов является сортировка по именам. Выполнить это можно при помощи тега xsl:sort (см. Листинг 4).

Листинг 4. Код для сортировки имен авторов

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:template match="/">
        <xsl:variable name="allauthors">
            <authors>
                <xsl:for-each select="/books/book">
                    <xsl:sort select="@author" />
                    <author id="{@author}" />
                </xsl:for-each>
            </authors>
        </xsl:variable>
        <xsl:copy-of select="$allauthors" />
    </xsl:template>
</xsl:stylesheet>

Единственным отличием этого кода от Листинга 2 является использование тега xsl:sort внутри цикла xsl:for-each. Тег xsl:sort позволяет отсортировать теги в цикле по заданному значению. В качестве ключа для сортировки мы используем атрибут author. В Листинге 5 показан результат преобразования данного шаблона.

Листинг 5. Отсортированный список

<?xml version="1.0" encoding="UTF-8"?>
<authors>
    <author id="Dave Thomas"/>
    <author id="Dave Thomas"/>
    <author id="Jack Herrington"/>
</authors>

Теперь остается сократить список, удалив все повторяющиеся записи.

Сокращение списка авторов

Найти уникальные записи в списке можно при помощи осей XPath. В нашем случае мы выполним итерацию оригинального списка и создадим новый, задав при этом условие, что если текущий элемент является таким же, как и предыдущий, то он будет игнорироваться. Для определения последнего элемента в Xpath используется ось preceding-sibling. В Листинге 6 представлен пример использования данной оси.

Листинг 6. Код для создания списка уникальных авторов

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:template match="/">
        <xsl:variable name="allauthors">
            <authors>
                <xsl:for-each select="/books/book">
                    <xsl:sort select="@author" />
                    <author id="{@author}" />
                </xsl:for-each>
            </authors>
        </xsl:variable>
        <xsl:variable name="authors">
            <authors>
                <xsl:for-each select="$allauthors/authors/author">
                    <xsl:if test="not(preceding-sibling::author/@id=./@id)">
                        <xsl:copy-of select="." />
                    </xsl:if>
                </xsl:for-each>
            </authors>
        </xsl:variable>
        <xsl:copy-of select="$authors" />
    </xsl:template>
</xsl:stylesheet>

С первого раза у вас, скорее всего, ничего не получится. Создание переменной $allauthors происходит также как и в прошлый раз, только сейчас мы создаем новую переменную $authors, используя цикл xsl:for-each для переменной $allauthors. Основное действие происходит при проверке условия xsl:if, где мы сравниваем текущий атрибут id с атрибутом id предыдущего элемента.

Оси XPath вначале могут показаться запутанными, однако их использование может вам крайне пригодиться. Попробуйте, и вы поймете, насколько они удобны. В Листинге 7 представлен конечный отсортированный список всех авторов. Листинг 7. Окончательный отсортированный список отдельных авторов

<?xml version="1.0" encoding="UTF-8"?>
<authors>
    <author id="Dave Thomas" />
    <author id="Jack Herrington" />
</authors>

Резюме

Если вы не можете изменить структуру исходного XML, можно изменить ее на своей стороне с помощью XSLT-преобразований. XSLT 2.0 предоставляет возможность сделать это путем создания в памяти промежуточных XML-деревьев.



31 марта 2005 г.
Джек Херрингтон
http://www.ibm.com/developerworks/ru/library/x-tiptwist/index.html