Язык Java традиционно широко поддерживает XML-технологии: большинство передовых разработок в этой области реализуется, как правило, сначала на Java и уж затем переносится на другие платформы разработки.
Не стал исключением и XSLT. Можно смело сказать, что количество XSLT-средств, написанных на Java, превосходит половину вообще всех существующих в настоящее время XSLT-пакетов.
Для того чтобы продемонстрировать использование XSLT в Java, мы приведём два варианта одной и той же программы — серверного приложения (сервлета), которое по запросу клиента будет возвращать информацию о текущем HTTP-сеансе в формате HTML.
Первый вариант сервлета можно назвать «традиционным». В нём HTML-документ создаётся серией инструкций out.println(...), которые выводят в выходящий поток размеченную HTML-тегами информацию о текущем сеансе.
Пример 9.22. Традиционный вариант сервлета
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class example extends HttpServlet { /** * Инициализация. */ public void init(ServletConfig config) throws ServletException { super.init(config); } /** * Основной метод сервлета */ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Выставляем тип содержимого response.setContentType("text/html"); // Инициализируем выходящий поток OutputStreamWriter osw = new OutputStreamWriter(response.getOutputStream()); PrintWriter out = new PrintWriter (response.getOutputStream()); // Выполняем вывод HTML-страницы out.println("<html>"); // Выводим головную часть HTML-документа out.println(" <head>"); out.println(" <title>Request information</title>"); out.println(" </head>"); // Выводим тело документа out.println(" <body>"); // Выводим общую информацию о запросе out.println(" <h1>General information</h1>"); out.println(" <table>"); // Выводим имя сервера out.println(" <tr>"); out.println(" <td>Server name</td>"); out.println(" <td>" + request.getServerName() + "</td>"); out.println(" </tr>"); // Выводим порт сервера out.println(" <tr>"); out.println(" <td>Server port</td>"); out.println(" <td>" + request.getServerPort() + "</td>"); out.println(" </tr>"); // Выводим адрес запрашивающей стороны out.println(" <tr>"); out.println(" <td>Remote address</td>"); out.println(" <td>" + request.getRemoteAddr() + "</td>"); out.println(" </tr>"); // Выводим название протокола запроса out.println(" <tr>"); out.println(" <td>Protocol</td>"); out.println(" <td>" + request.getProtocol() + "</td>"); out.println(" </tr>"); // Выводим метод запроса out.println(" <tr>"); out.println(" <td>Method</td>"); out.println(" <td>" + request.getMethod() + "</td>"); out.println(" </tr>"); // Выводим URI запроса out.println(" <tr>"); out.println(" <td>Request URI</td>"); out.println(" <td>" + request.getRequestURI() + "</td>"); out.println(" </tr>"); // Выводим строку запроса out.println(" <tr>"); out.println(" <td>Query String</td>"); out.println(" <td>" + request.getQueryString() + "</td>"); out.println(" </tr>"); out.println(" </table>"); // Выводим параметры запроса out.println(" <h1>Request parameters</h1>"); out.println(" <table>"); for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) { String name = e.nextElement().toString(); String[] values = request.getParameterValues(name); for (int i=0; i < values.length; i++) { out.println(" <tr>"); out.println(" <td>" + name + "</td>"); out.println(" <td>" + values[i] + "</td>"); out.println(" </tr>"); } } out.println(" </table>"); // Выводим параметры HTTP-сессии out.println(" <h1>Session parameters</h1>"); out.println(" <table>"); HttpSession session = request.getSession(true); String[] names = session.getValueNames(); for (int i=0; i < names.length; i++) { String name = session.getValueNames()[i]; out.println(" <tr>"); out.println(" <td>" + name + "</td>"); out.println(" <td>" + session.getValue(name).toString() + "</td>"); out.println(" </tr>"); } out.println(" </table>"); // Выводим cookies response.addCookie(new Cookie("content", "apple jam")); out.println(" <h1>Cookies</h1>"); out.println(" <table>"); Cookie[] cookies = request.getCookies(); for (int i=0; i < cookies.length; i++) { out.println(" <tr>"); out.println(" <td>" + cookies[i].getName() + "</td>"); out.println(" <td>" + cookies[i].getValue() + "</td>"); out.println(" </tr>"); } out.println(" </table>"); out.println(" </body>"); out.println("</html>"); // Закрываем выходящий поток out.close(); } }
Результатом обращения к этому сервлету по URL вида
http://localhost/servlet/example?x=1&y=2&z=3&x=4&y=5&z=6
будет документ, аналогичный представленному на рис 9.13.
Несложно видеть, насколько жёстко в этом сервлете закодирована презентация данных: для минимального изменения генерируемого документа придётся в обязательном порядке изменять сам сервлет, что в современных системах может быть непозволительной роскошью, — всё равно, что перебирать мотор для того, чтобы перекрасить автомобиль.
Второй вариант того же самого сервлета, который мы предложим ниже, демонстрирует, как в данном случае при помощи XSLT можно разделить данные и их презентацию. Идея очень проста: вместо того, чтобы в жёстко заданном виде выводить информацию в выходящий поток, можно создать XML-документ в виде DOM-объекта и затем применить к нему XSLT-преобразование, которое создаст для него требуемое HTML-представление.
В этом варианте сервлета мы будем использовать Java-версию XML-библиотеки Oracle XDK (Oracle XML SDK, платформа разработки XML-приложений, созданная в Oracle Corp.). В данном примере из этой библиотеки мы будем использовать только XSLT-процессор (класс XSLProcessor) и реализацию DOM-модели XML-документа (класс XMLDocument). Во всём остальном мы будем полагаться на Java-реализацию стандартных интерфейсов объектной модели документа DOM, разработанной Консорциумом W3. DOM-интерфейсы позволят нам манипулировать XML-документом на уровне модели: создавать и включать друг в друга узлы элементов, текстовые узлы и так далее.
Пример 9.23. Вариант сервлета, использующий XSLT
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; import java.net.*; import oracle.xml.parser.v2.*; import org.w3c.dom.*; public class example extends HttpServlet { /** * Функция, создающая в элементе parent элемент с именем name и * текстовым значением value. Если value имеет значение null, * текст не создаётся. */ public static Element addElement(Element parent, String name, String value) { Element child = parent.getOwnerDocument().createElement(name); parent.appendChild(child); if (value != null) { Text text = parent.getOwnerDocument().createTextNode(value); child.appendChild(text); } return child; } /** * Инициализация. */ public void init(ServletConfig config) throws ServletException { super.init(config); } /** * Основной метод сервлета */ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Выставляем тип содержимого response.setContentType("text/html"); // Инициализируем выходящий поток OutputStreamWriter osw = new OutputStreamWriter(response.getOutputStream()); PrintWriter out = new PrintWriter (response.getOutputStream()); // Получаем объекты cookie Cookie[] cookies = request.getCookies(); // Создаём выходящий документ XMLDocument doc = new XMLDocument(); // Создаём корневой элемент Request Element elRequest = doc.createElement("Request"); doc.appendChild(elRequest); // Создаём элемент General Element elGeneral = addElement(elRequest, "General", null); // Создаём элементы, содержащие общую информацию addElement(elGeneral, "ServerName", request.getServerName()); addElement(elGeneral, "ServerPort", Integer.toString(request.getServerPort())); addElement(elGeneral, "RemoteAddr", request.getRemoteAddr()); addElement(elGeneral, "Protocol", request.getProtocol()); addElement(elGeneral, "Method", request.getMethod()); addElement(elGeneral, "RequestURI", request.getRequestURI()); addElement(elGeneral, "QueryString", request.getQueryString()); // Cоздаём элемент Param Element elParam = addElement(elRequest, "Param", null); // В элементе Param создаём элементы, описывающие параметры запроса for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) { String name = e.nextElement().toString(); String[] values = request.getParameterValues(name); // Для каждого из значений каждого из параметров // создаём соответствующий элемент for (int i=0; i < values.length; i++) addElement(elParam, name, values[i]); } // Создаём элемент Session Element elSession = addElement(elRequest, "Session", null); // Получаем объект HTTP-сессии HttpSession session = request.getSession(true); // Получаем имена параметров сессии String[] names = session.getValueNames(); // В элементе Session создаём по элементу //для каждого из параметров сессии for (int i=0; i < names.length; i++) addElement(elSession, session.getValueNames()[i], session.getValue(session.getValueNames()[i]).toString()); // Создаём элемент Cookie Element elCookie = addElement(elRequest,"Cookie", null); // Создаём по элементу для каждого из объектов cookies for (int i=0; i < cookies.length; i++) addElement(elCookie, cookies[i].getName(), cookies[i].getValue()); // Преобразовываем созданный документ и выводим результат try { // Загружаем преобразование XSLStylesheet stylesheet = new XSLStylesheet( new URL("http://localhost/stylesheet.xsl"), null); // Выполняем преобразование XMLDocumentFragment fragment = (XMLDocumentFragment) doc.transformNode(stylesheet); // Выводим результат fragment.print(out); } catch (MalformedURLException mue) {} catch (XSLException xsle) {} // Закрываем выходящий поток out.close(); } }
В этом сервлете вместо того, чтобы просто печатать в выходящий поток данные и HTML-разметку, в переменной doc мы генерируем DOM-объект XML-документа. После того, как все текстовые узлы и узлы элементов будут сгенерированы, документ, содержащийся в переменной doc примет приблизительно следующий вид.
Пример 9.24. XML-документ, сгенерированный в сервлете
<Request> <General> <ServerName>aphrodite.fzi.de</ServerName> <ServerPort>80</ServerPort> <RemoteAddr>127.0.0.1</RemoteAddr> <Protocol>HTTP/1.1</Protocol> <Method>GET</Method> <RequestURI>/servlet/example1</RequestURI> <QueryString>x=1&y=2&z=3&x=4&y=5&z=6 </QueryString> </General> <Param> <z>3</z> <z>6</z> <y>2</y> <y>5</y> <x>1</x> <x>4</x> </Param> <Session> <v>4</v> </Session> <Cookie> <content>apple jam</content> <JServSessionIdroot>aaenbyjqc0</JServSessionIdroot> </Cookie> </Request>
После того, как генерация документа завершена, к нему применяется преобразование stylesheet.xsl, которое создаёт его HTML-представление:
Пример 9.25. Преобразование stylesheet.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="Request"> <html> <head> <title>Request information</title> </head> <body><xsl:apply-templates mode="table"/></body> </html> </xsl:template> <xsl:template match="*" mode="table"> <h1><xsl:apply-templates select="." mode="header"/></h1> <table><xsl:apply-templates mode="row"/></table> </xsl:template> <xsl:template match="General" mode="header"> <xsl:text>General information</xsl:text> </xsl:template> <xsl:template match="Param" mode="header"> <xsl:text>Request parameters</xsl:text> </xsl:template> <xsl:template match="Session" mode="header"> <xsl:text>Session parameters</xsl:text> </xsl:template> <xsl:template match="Cookie" mode="header"> <xsl:text>Cookies</xsl:text> </xsl:template> <xsl:template match="*" mode="row"> <tr> <td><xsl:apply-templates select="." mode="name"/></td> <td><xsl:value-of select="."/></td> </tr> </xsl:template> <xsl:template match="*" mode="name"> <xsl:value-of select="name()"/> </xsl:template> <xsl:template match="General/ServerName" mode="name"> <xsl:text>Server name</xsl:text> </xsl:template> <xsl:template match="General/ServerPort" mode="name"> <xsl:text>Server port</xsl:text> </xsl:template> <xsl:template match="General/RemoteAddr" mode="name"> <xsl:text>Remote address</xsl:text> </xsl:template> <xsl:template match="General/RequestURI" mode="name"> <xsl:text>Request URI</xsl:text> </xsl:template> <xsl:template match="General/QueryString" mode="name"> <xsl:text>Query string</xsl:text> </xsl:template> </xsl:stylesheet>
Результатом этого преобразования является следующий HTML-документ, внешний вид которого полностью идентичен документу, показанному на рис. 9.13.
Пример 9.26. Результирующий HTML-документ
<html> <head> <title>Request information</title> </head> <body> <h1>General information</h1> <table> <tr> <td>Server name</td> <td>aphrodite.fzi.de</td> </tr> <tr> <td>Server port</td> <td>80</td> </tr> <tr> <td>Remote address</td> <td>127.0.0.1</td> </tr> <tr> <td>Protocol</td> <td>HTTP/1.1</td> </tr> <tr> <td>Method</td> <td>GET</td> </tr> <tr> <td>Request URI</td> <td>/servlet/example1</td> </tr> <tr> <td>Query string</td> <td>x=1&y=2&z=3&x=4&y=5&z=6</td> </tr> </table> <h1>Request parameters</h1> <table> <tr> <td>z</td> <td>3</td> </tr> <tr> <td>z</td> <td>6</td> </tr> <tr> <td>y</td> <td>2</td> </tr> <tr> <td>y</td> <td>5</td> </tr> <tr> <td>x</td> <td>1</td> </tr> <tr> <td>x</td> <td>4</td> </tr> </table> <h1>Session parameters</h1> <table> <tr> <td>v</td> <td>4</td> </tr> </table> <h1>Cookies</h1> <table> <tr> <td>content</td> <td>apple jam</td> </tr> <tr> <td>JServSessionIdroot</td> <td>aaenbyjqc0</td> </tr> </table> </body> </html>
Второй вариант сервлета, конечно, не проще, чем первый, да и вряд ли он будет быстрее и экономичнее с точки зрения памяти, ведь вместо простого вывода текста в поток мы сначала создаём в памяти объектную модель документа, преобразуем её и только затем выводим результат. Однако главное, чего удалось в этом случае добиться — это отделение данных от их презентации.
Представим, к примеру, что нам потребовалось перевести названия полей выводимого документа на русский язык — получить текст «Общая информация» вместо «General information» и так далее. В первом случае для внесения этого элементарного представления потребуется переписывать, перекомпилировать и обновлять на сервере сервлет; во втором случае всё, что нужно будет сделать — это исправить несколько строк в файле stylesheet.xsl.