JSTL: Шаблоны для разработки веб-приложений в java. Часть 2

В прошлый раз я рассказал о тегах основного назначения, сегодня самое время перейти к средствам позволяющим работать с xml. Прежде всего, мы должны в заголовке jsp страницы подключить следующую библиотеку тегов:

<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>

Во всех последующих примерах будет нужен xml-файл, вот его код:

<?xml version="1.0" encoding="utf-8"?>
<bookshelf>
    <book id="12">
        <title>Книга про зайцев</title>
        <description><![CDATA[Книга &copy;рассказывает про суровую
 судьбу молодой семьи зайцев, живущих в самый разгар ...]]></description>
        <price>99.95</price>
        <authors>
            <author main="true">Тапкин Василий Васильевич</author>
            <author>Пупкина Ленка Ленковна</author>
        </authors>
    </book>
    <book id="13">
        <title>Слоны-людоеды</title>
        <description><![CDATA[Книга создана по материалам расследования серии
 жестоких убийств в местном зоопарке]]></description>
        <price>29.95</price>
        <authors>
            <author main="true">Слоноглазов Глеб Гамбитович</author>
            <author>Слоноухова Виктория Мракобесовна</author>
        </authors>
    </book>
</bookshelf>

До начала работы с xml его нужно распарсить, делается это либо непосредственно внутри jsp-файла либо подобную работу может выполнить java backend-код. Парсинг выполняется средствами тега x:parse, например, так:

<x:parse var="bookshelf">
<?xml version="1.0" encoding="utf-8"?>
<bookshelf>
    <book id="12">
        <title>Книга про зайцев</title>
        <description><![CDATA[Книга &copy;рассказывает про суровую судьбу
 молодой семьи зайцев, живущих в самый разгар ...]]></description>
        <price>99.95</price>
        <authors>
            <author main="true">Тапкин Василий Васильевич</author>
            <author>Пупкина Ленка Ленковна</author>
        </authors>
    </book>
    <book id="13">
        <title>Слоны-людоеды</title>
        <description><![CDATA[Книга создана по материалам расследования
 серии жестоких убийств в местном зоопарке]]></description>
        <price>29.95</price>
        <authors>
            <author main="true">Слоноглазов Глеб Гамбитович</author>
            <author>Слоноухова Виктория Мракобесовна</author>
        </authors>
    </book>
</bookshelf> 
</x:parse>
<x:parse var="bookshelf2" xml="${helloMachine.bookshelfAsString}"  />

Если в первом случае весь код xml находится внутри тега parse, то второй вариант предполагает, что в составе класса HelloMachineBean должен появиться новый метод (для как бы не настоящего свойства), который читал бы с диска приведенный ранее xml-документ, и возвращал бы его в jstl-код в виде обычной строки.

public String getBookshelfAsString() {
  
try {
     
BufferedReader brin = new BufferedReader(
           
new InputStreamReader(getClass().getResourceAsStream("/testi/templates/book.xml")));
      StringBuffer buf =
new StringBuffer();
      String line;
     
while ((line = brin.readLine()) != null)
        
buf.append(line);
     
return buf.toString();
  
} catch (IOException e) {
     
e.printStackTrace();
     
return null;
  
}
}

Теперь попробуем с этим xml-файлом что-нибудь сделать. В самом простом случае нужно вывести на экран какую-то информацию. Обычный c:out не подходит, нам нужен тег, который выбирает с помощью xpath-выражений. И такой тег есть – x:out, в качестве параметров для него указывается атрибут select с выражением вида:

Где-искать/Чего-искать

Где искать – это java-переменная ссылающаяся на xml-дерево. Имя ее может быть как просто bookshelf (см. прошлый пример), так и содержать имя контекста, например: pageScope:bookshelf.

Вот примеры, отбирающие разные части xml-документа (пусть вас не смущает то, что у меня и имя переменной в которой хранится xml-документ и имя первого тега совпадают – это ничего не значит):

<x:parse var="bookshelf">
    <c:import url="book.xml" charEncoding="utf-8" />
</x:parse>
 
all book content:<x:out select="$bookshelf/bookshelf/book[1]" escapeXml="false"/>
id:<x:out select="$bookshelf/bookshelf/book[1]/@id" />
title:<x:out select="$bookshelf/bookshelf/book[1]/title" />
qty authors:<x:out select="count($bookshelf/bookshelf/book[1]/authors/author)" />

Атрибут escapeXML служит для управления тем, что будет происходить при обработке сущностей xml. Так в ранее приведенном примере документа, у меня внутри тега description находится символьный контейнер CDATA, внутри текста которой есть сущность ©. В результате вывода с включенным escapeXML я вижу именно ©, когда же режим экранирования отключен, то вижу значок копирайта (собственно, это аналог атрибута из xsl – disable-output-escaping).

Теперь проблема: если я к xml-документу добавлю пространства имен, например, так:

<bookshelf xmlns="http://books.com/buki">

То ранее приведенный код не будет работать. Выходом будет записать выражение, основанное не на полных именах тегов, а на коротких именах (локальных именах):

all book content:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]" escapeXml="false"/>
 
id:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]/@id" />
title:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]/*[name()='title']" />
qty authors:<x:out select="count($bookshelf/*[name()='bookshelf']/*[name()='book'][1]/*[name()='authors']/*[name()='author'])" />

Подобные выражения, мягко говоря, не удобочитаемы, но лучшего выхода из сложившейся проблемы нет (точнее есть: плюнуть на jstl и написать самому).

При записи сложных xpath-выражений можно ссылаться на объявленные в jstl переменные, например, далее я хочу найти в списке книг только ту, номер которой был передан как входной параметр скрипту:

book title by bid: <x:out select="$bookshelf/bookshelf/book[@id = $param:bid]/title" />

Теперь перейдем к тегу x:set. Он очень похож на x:out, вот только не выводит на экран некоторую xpath-часть входного документа, а присваивает ее jstl-переменной, например, для того чтобы в последующем еще раз выполнить обработку документа с помощью x:out (ни в коем случае полученный промежуточный узел нельзя выводить c:out):

<x:set var="bookTitle" select="$bookshelf/bookshelf/book[1]/title" />
book title = <x:out select="$bookTitle/." />

Теперь разберемся с тем, как в xml работать с циклами и условиями. В стандартной части jstl есть тег forEach умеющий работать со списками и массивами. В части jstl посвященной xml есть одноименный и очень похожий по параметрами тег forEach. Вот пример использования двойного цикла (в виде заголовка название книги, далее в виде списка авторы):

<x:forEach select="$bookshelf/bookshelf/book" var="book">
    <h1>
        <x:out select="$book/title" />
    </h1>
    <ul>
        <x:forEach select="$book/authors/author" var="author">
            <li>
                <x:out select="$author" />
            </li>
        </x:forEach>
    </ul>
</x:forEach>

Для работы с условиями есть два тега: if и choose, точь-в-точь как их старшие братья. Далее идет пример, в котором для каждой книги выводится сообщение “два или более автора” если у книги, действительно, более чем один автор:

<x:forEach select="$bookshelf/bookshelf/book" var="book">
    <h1>
        <x:out select="$book/title" />
    </h1>
    <x:if select="count($book/authors/author) >= 2">
        Два или более писателя сотворили сей опус
    </x:if>
</x:forEach>

Также как и для c:if, у тега x:if есть необязательные атрибуты (var и scope) позволяющие не только выполнить проверку условия, но и сохранить ее в некоторую переменную для последующего многократного использования (в этом случае указывать тело условия не обязательно). В следующем примере показывается прием, когда в условии мы можем обращаться к переменным полученным как параметр формы.

<x:if select="$bookshelf/bookshelf/book[@id = $param:bid]" var="xxx" />

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

<x:forEach select="$bookshelf/bookshelf/book" var="book">
    <h1>
        <x:out select="$book/title" />
    </h1>
    <x:choose>
        <x:when select="count($book/authors/author) = 0">
            У книги нет авторов, вообще.
        </x:when>
        <x:when select="count($book/authors/author) = 1">
            Только один автор
        </x:when>
        <x:otherwise>
            Количество авторов неизвестно, но их точно более чем один
        </x:otherwise>
    </x:choose>
</x:forEach>

Последний тег в разделе посвященном работе с xml – это x:transform. Его назначение выполнять xsl-трансформацию некоторого xml-документа с помощью xsl-документа в другой xml или html-документ. Для следующего примера я создал небольшой xsl-файлик:

transbook.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
 
    <xsl:template match="/">
        <xsl:for-each select="bookshelf/book">
            <h1>
                <xsl:value-of select="title" disable-output-escaping="yes" />
            </h1>
            <ul>
                <xsl:for-each select="authors/author">
                    <li>
                        <xsl:value-of select="." disable-output-escaping="yes" />
                    </li>
                </xsl:for-each>
            </ul>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Для дальнейшего примера вам нужно удостовериться, что в папке lib веб-приложения есть библиотека xerces, а теперь, поехали дальше.

У тега transform есть два обязательных атрибута: xml – содержит входные данные для трансформации, и атрибут xslt, задающий правила по которым трансформация будет выполнена. Вот только … неожиданный момент, что оба входные параметр xslt – это не xml-документы созданный с помощью x:parse, а строки текста с их значениями.

<c:set var="bookshelf">
    <c:import url="book.xml" charEncoding="utf-8" />
</c:set>
 
<c:set var="transbook">
    <c:import url="transbook.xsl" charEncoding="utf-8" />
</c:set>
 
<x:transform xml="${bookshelf}" xslt="${transbook}" />

Одна из приятных (правка никогда не использованных мною) возможностей – это создание из тегов transform настоящей цепочки обработки данных. Когда результат первого преобразования xml-я подается на вход еще одному тегу transform, а результат второго преобразования – на вход третьему …

<c:set var="transbook_0">
    <c:import url="transbook_0.xsl" charEncoding="utf-8" />
</c:set>

<c:set var="transbook_1">
    <c:import url="transbook_1.xsl" charEncoding="utf-8" />
</c:set>

<c:set var="transbook_2">
    <c:import url="transbook_2.xsl" charEncoding="utf-8" />
</c:set>
 
<x:transform xslt="${transbook_2}">
    <x:transform xslt="${transbook_1}">
        <x:transform xslt="${transbook_0}" xml="${bookshelf}"/>
    </x:transform>
</x:transform>

Еще одна полезная методика позволяющая управлять выполнением трансформаций – это передача в xsl-файл переменных извне. Я обновил xsl-файл, добавил внутри корневого элемента, элемента param, а затем внутри template обрабатывающего элемент “/” я вывел переменную с именем fio. Откуда же она взялась?

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
 
    <xsl:param name="fio" />
 
    <xsl:template match="/">
        <h1>
            <xsl:value-of select="$fio" disable-output-escaping="yes" />
        </h1>
        <xsl:for-each select="bookshelf/book">
            <h1>
                <xsl:value-of select="title" disable-output-escaping="yes" />
            </h1>
            <ul>
                <xsl:for-each select="authors/author">
                    <li>
                        <xsl:value-of select="." disable-output-escaping="yes" />
                    </li>
                </xsl:for-each>
            </ul>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

А переменная поступила из jstl-кода (единственный недостаток в том, что нельзя передать внутрь xsl-файла произвольный узел xml – только строки):

<x:transform xml="${bookshelf}" xslt="${transbook}" >
    <x:param name="fio" value="Petyano" />
</x:transform>

Источник — http://black-zorro.com/mediawiki/JSTL:_Шаблоны_для_разработки_веб-приложений_в_java._Часть_2