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

June 19, 2008

В прошлый раз я рассказал о тегах основного назначения, сегодня самое время перейти к средствам позволяющим работать с xml. Прежде всего, мы должны в заголовке jsp страницы подключить следующую библиотеку тегов:
  1. <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
Во всех последующих примерах будет нужен xml-файл, вот его код:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <bookshelf>
  3.     <book id="12">
  4.         <title>Книга про зайцев</title>
  5.         <description><![CDATA[Книга �рассказывает про суровую
  6.  судьбу молодой семьи зайцев, живущих в самый разгар ...]]></description>
  7.         <price>99.95</price>
  8.         <authors>
  9.             <author main="true">Тапкин Василий Васильевич</author>
  10.             <author>Пупкина Ленка Ленковна</author>
  11.         </authors>
  12.     </book>
  13.     <book id="13">
  14.         <title>Слоны-людоеды</title>
  15.         <description><![CDATA[Книга создана по материалам расследования серии
  16.  жестоких убийств в местном зоопарке]]></description>
  17.         <price>29.95</price>
  18.         <authors>
  19.             <author main="true">Слоноглазов Глеб Гамбитович</author>
  20.             <author>Слоноухова Виктория Мракобесовна</author>
  21.         </authors>
  22.     </book>
  23. </bookshelf>
До начала работы с xml его нужно распарсить, делается это либо непосредственно внутри jsp-файла либо подобную работу может выполнить java backend-код. Парсинг выполняется средствами тега x:parse, например, так:
  1. <x:parse var="bookshelf">
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <bookshelf>
  4.     <book id="12">
  5.         <title>Книга про зайцев</title>
  6.         <description><![CDATA[Книга �рассказывает про суровую судьбу
  7.  молодой семьи зайцев, живущих в самый разгар ...]]></description>
  8.         <price>99.95</price>
  9.         <authors>
  10.             <author main="true">Тапкин Василий Васильевич</author>
  11.             <author>Пупкина Ленка Ленковна</author>
  12.         </authors>
  13.     </book>
  14.     <book id="13">
  15.         <title>Слоны-людоеды</title>
  16.         <description><![CDATA[Книга создана по материалам расследования
  17.  серии жестоких убийств в местном зоопарке]]></description>
  18.         <price>29.95</price>
  19.         <authors>
  20.             <author main="true">Слоноглазов Глеб Гамбитович</author>
  21.             <author>Слоноухова Виктория Мракобесовна</author>
  22.         </authors>
  23.     </book>
  24. </bookshelf> 
  25. </x:parse>
  1. <x:parse var="bookshelf2" xml="${helloMachine.bookshelfAsString}"  />
Если в первом случае весь код xml находится внутри тега parse, то второй вариант предполагает, что в составе класса HelloMachineBean должен появиться новый метод (для как бы не настоящего свойства), который читал бы с диска приведенный ранее xml-документ, и возвращал бы его в jstl-код в виде обычной строки.
  1. public String getBookshelfAsString() {
  2.         try {
  3.             BufferedReader brin = new BufferedReader(
  4.   new InputStreamReader(getClass().getResourceAsStream("/testi/templates/book.xml")));
  5.             StringBuffer buf = new StringBuffer();
  6.             String line;
  7.             while ((line = brin.readLine()) != null)
  8.                 buf.append(line);
  9.             return buf.toString();
  10.         } catch (IOException e) {
  11.             e.printStackTrace();
  12.             return null;
  13.         }
  14.     }
Теперь попробуем с этим xml-файлом что-нибудь сделать. В самом простом случае нужно вывести на экран какую-то информацию. Обычный c:out не подходит, нам нужен тег, который выбирает с помощью xpath-выражений. И такой тег есть – x:out, в качестве параметров для него указывается атрибут select с выражением вида:
 Где-искать/Чего-искать
Где искать – это java-переменная ссылающаяся на xml-дерево. Имя ее может быть как просто bookshelf (см. прошлый пример), так и содержать имя контекста, например: pageScope:bookshelf.

Вот примеры, отбирающие разные части xml-документа (пусть вас не смущает то, что у меня и имя переменной в которой хранится xml-документ и имя первого тега совпадают – это ничего не значит):
  1. <x:parse var="bookshelf">
  2.     <c:import url="book.xml" charEncoding="utf-8" />
  3. </x:parse>
  4.  
  5. all book content:<x:out select="$bookshelf/bookshelf/book[1]" escapeXml="false"/>
  6. id:<x:out select="$bookshelf/bookshelf/book[1]/@id" />
  7. title:<x:out select="$bookshelf/bookshelf/book[1]/title" />
  8. qty authors:<x:out select="count($bookshelf/bookshelf/book[1]/authors/author)" />
Атрибут escapeXML служит для управления тем, что будет происходить при обработке сущностей xml. Так в ранее приведенном примере документа, у меня внутри тега description находится символьный контейнер CDATA, внутри текста которой есть сущность ©. В результате вывода с включенным escapeXML я вижу именно ©, когда же режим экранирования отключен, то вижу значок копирайта (собственно, это аналог атрибута из xsl – disable-output-escaping).

Теперь проблема: если я к xml-документу добавлю пространства имен, например, так:
  1. <bookshelf xmlns="http://books.com/buki">
То ранее приведенный код не будет работать. Выходом будет записать выражение, основанное не на полных именах тегов, а на коротких именах (локальных именах):
  1. all book content:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]" escapeXml="false"/>
  2.  
  3. id:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]/@id" />
  4. title:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]/*[name()='title']" />
  5. qty authors:<x:out select="count($bookshelf/*[name()='bookshelf']/*[name()='book'][1]/*[name()='authors']/*[name()='author'])" />
Подобные выражения, мягко говоря, не удобочитаемы, но лучшего выхода из сложившейся проблемы нет (точнее есть: плюнуть на jstl и написать самому).

При записи сложных xpath-выражений можно ссылаться на объявленные в jstl переменные, например, далее я хочу найти в списке книг только ту, номер которой был передан как входной параметр скрипту:
  1. book title by bid: <x:out select="$bookshelf/bookshelf/book[@id = $param:bid]/title" />
Теперь перейдем к тегу x:set. Он очень похож на x:out, вот только не выводит на экран некоторую xpath-часть входного документа, а присваивает ее jstl-переменной, например, для того чтобы в последующем еще раз выполнить обработку документа с помощью x:out (ни в коем случае полученный промежуточный узел нельзя выводить c:out):
  1. <x:set var="bookTitle" select="$bookshelf/bookshelf/book[1]/title" />
  2. book title = <x:out select="$bookTitle/." />
Теперь разберемся с тем, как в xml работать с циклами и условиями. В стандартной части jstl есть тег forEach умеющий работать со списками и массивами. В части jstl посвященной xml есть одноименный и очень похожий по параметрами тег forEach. Вот пример использования двойного цикла (в виде заголовка название книги, далее в виде списка авторы):
  1. <x:forEach select="$bookshelf/bookshelf/book" var="book">
  2.     <h1>
  3.         <x:out select="$book/title" />
  4.     </h1>
  5.     <ul>
  6.         <x:forEach select="$book/authors/author" var="author">
  7.             <li>
  8.                 <x:out select="$author" />
  9.             </li>
  10.         </x:forEach>
  11.     </ul>
  12. </x:forEach>
Для работы с условиями есть два тега: if и choose, точь-в-точь как их старшие братья. Далее идет пример, в котором для каждой книги выводится сообщение “два или более автора” если у книги, действительно, более чем один автор:
  1. <x:forEach select="$bookshelf/bookshelf/book" var="book">
  2.     <h1>
  3.         <x:out select="$book/title" />
  4.     </h1>
  5.     <x:if select="count($book/authors/author) >= 2">
  6.         Два или более писателя сотворили сей опус
  7.     </x:if>
  8. </x:forEach>
Также как и для c:if, у тега x:if есть необязательные атрибуты (var и scope) позволяющие не только выполнить проверку условия, но и сохранить ее в некоторую переменную для последующего многократного использования (в этом случае указывать тело условия не обязательно). В следующем примере показывается прием, когда в условии мы можем обращаться к переменным полученным как параметр формы.
  1. <x:if select="$bookshelf/bookshelf/book[@id = $param:bid]" var="xxx" />
Теперь разберем то, как работать со сложными условиями, когда у нас две или более ветви вычислений:
  1. <x:forEach select="$bookshelf/bookshelf/book" var="book">
  2.     <h1>
  3.         <x:out select="$book/title" />
  4.     </h1>
  5.     <x:choose>
  6.         <x:when select="count($book/authors/author) = 0">
  7.             У книги нет авторов, вообще.
  8.         </x:when>
  9.         <x:when select="count($book/authors/author) = 1">
  10.             Только один автор
  11.         </x:when>
  12.         <x:otherwise>
  13.             Количество авторов неизвестно, но их точно более чем один
  14.         </x:otherwise>
  15.     </x:choose>
  16. </x:forEach>
Последний тег в разделе посвященном работе с xml – это x:transform. Его назначение выполнять xsl-трансформацию некоторого xml-документа с помощью xsl-документа в другой xml или html-документ. Для следующего примера я создал небольшой xsl-файлик:
 transbook.xsl
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  3. 	<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  4.  
  5. 	<xsl:template match="/">
  6. 		<xsl:for-each select="bookshelf/book">
  7. 			<h1>
  8. 				<xsl:value-of select="title" disable-output-escaping="yes" />
  9. 			</h1>
  10. 			<ul>
  11. 				<xsl:for-each select="authors/author">
  12. 					<li>
  13. 						<xsl:value-of select="." disable-output-escaping="yes" />
  14. 					</li>
  15. 				</xsl:for-each>
  16. 			</ul>
  17. 		</xsl:for-each>
  18. 	</xsl:template>
  19. </xsl:stylesheet>
Для дальнейшего примера вам нужно удостовериться, что в папке lib веб-приложения есть библиотека xerces, а теперь, поехали дальше.

У тега transform есть два обязательных атрибута: xml – содержит входные данные для трансформации, и атрибут xslt, задающий правила по которым трансформация будет выполнена. Вот только … неожиданный момент, что оба входные параметр xslt – это не xml-документы созданный с помощью x:parse, а строки текста с их значениями.
  1. <c:set var="bookshelf">
  2.     <c:import url="book.xml" charEncoding="utf-8" />
  3. </c:set>
  4.  
  5. <c:set var="transbook">
  6.     <c:import url="transbook.xsl" charEncoding="utf-8" />
  7. </c:set>
  8.  
  9.  
  10. <x:transform xml="${bookshelf}" xslt="${transbook}" />
Одна из приятных (правка никогда не использованных мною) возможностей – это создание из тегов transform настоящей цепочки обработки данных. Когда результат первого преобразования xml-я подается на вход еще одному тегу transform, а результат второго преобразования – на вход третьему …
  1. <c:set var="transbook_0">
  2.     <c:import url="transbook_0.xsl" charEncoding="utf-8" />
  3. </c:set>
  4. <c:set var="transbook_1">
  5.     <c:import url="transbook_1.xsl" charEncoding="utf-8" />
  6. </c:set>
  7. <c:set var="transbook_2">
  8.     <c:import url="transbook_2.xsl" charEncoding="utf-8" />
  9. </c:set>
  10.  
  11. <x:transform xslt="${transbook_2}">
  12.   <x:transform xslt="${transbook_1}">
  13.     <x:transform xslt="${transbook_0}" xml="${bookshelf}"/>
  14.   </x:transform>
  15. </x:transform>
Еще одна полезная методика позволяющая управлять выполнением трансформаций – это передача в xsl-файл переменных извне. Я обновил xsl-файл, добавил внутри корневого элемента, элемента param, а затем внутри template обрабатывающего элемент “/” я вывел переменную с именем fio. Откуда же она взялась?
  1. <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  2. 	<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  3.  
  4. 	<xsl:param name="fio" />
  5.  
  6. 	<xsl:template match="/">
  7. 		<h1>
  8. 			<xsl:value-of select="$fio" disable-output-escaping="yes" />
  9. 		</h1>
  10. 		<xsl:for-each select="bookshelf/book">
  11. 			<h1>
  12. 				<xsl:value-of select="title" disable-output-escaping="yes" />
  13. 			</h1>
  14. 			<ul>
  15. 				<xsl:for-each select="authors/author">
  16. 					<li>
  17. 						<xsl:value-of select="." disable-output-escaping="yes" />
  18. 					</li>
  19. 				</xsl:for-each>
  20. 			</ul>
  21. 		</xsl:for-each>
  22. 	</xsl:template>
  23. </xsl:stylesheet>
А переменная поступила из jstl-кода (единственный недостаток в том, что нельзя передать внутрь xsl-файла произвольный узел xml – только строки):
  1. <x:transform xml="${bookshelf}" xslt="${transbook}" >
  2.     <x:param name="fio" value="Petyano" />
  3. </x:transform>

Categories: Java