Выполнение 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&amp;y=2&amp;z=3&amp;x=4&amp;y=5&amp;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&amp;y=2&amp;z=3&amp;x=4&amp;y=5&amp;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.