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[Книга ©рассказывает про суровую судьбу молодой семьи зайцев, живущих в самый разгар ...]]></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[Книга ©рассказывает про суровую судьбу молодой семьи зайцев, живущих в самый разгар ...]]></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>
Источник —