Выполнение 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 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&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.