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

Материал из DOM

Перейти к: навигация, поиск


В прошлый раз я рассказал о тегах основного назначения, сегодня самое время перейти к средствам позволяющим работать с 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>

Subscribe Now!

 

ObMachine projects & articles (java, flash, flex, php, ...)  -- black-zorro.com