Выполнение XSLT-преобразований в Java
Выполнение XSLT-преобразований в Java
Язык 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.
Рис. 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 o_sw =
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());
// Создаем элемент 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.