JavaプログラムでXMLからPDFを生成する

今回は、前回に引き続きJavaプログラムからApache FOPを利用する方法を解説する。Javaプログラムから利用する場合にも、コマンドラインで実行する場合と同様にXML文書からXSLTによる変換を経由してPDF文書を生成することができる。

XSL-FOからPDFへの変換はJAXPに用意されたTransformarクラスを使用して行った。このTransformarオブジェクトは次のようにして生成できた。

リスト1

// 変換用オブジェクトの生成
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();

XMLからPDFに変換する場合にも、これと同様にTrabnsformarクラスを利用することができる。ただし、この場合の手順は(XML+XSTL) → (XSL-FO) → (PDF)であり、間にXSLTによるXSL-FOへの変換が入る。この変換を有効にするためには、Transformarオブジェクトを生成する際にコンストラクタにXSLT文書を指定すればよい。具体的にはSourceクラスを利用して次のように行う。これはxml2fo-sample.xslというXSLTファイルを使用して変換を行う場合のコード例である。

リスト2

// XSLTファイルの指定
Source xsltSource = new StreamSource("xml2fo-sample.xsl");
// 変換用オブジェクトの生成
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(xsltSource);

その他の部分は、transform()メソッドによる実際の変換も含めて(XSL-FO) → (PDF)の場合とまったく同様に記述することができる。XSLTを用いたXMLの変換処理をTransformarクラスが隠蔽してくれる点がApache FOP(およびJAXP)を利用する場合の大きなメリットである。以下に、XMLファイルとして「fop-sample-ja.xml」を、XSTLファイルとして「xml2fo-sample.xsl」を指定し、「fop-sample-ja.pdf」というPDFファイルを生成するプログラムのコード例を示す。fop-sample-ja.xmlとxml2fo-sample.xslの内容は前回示した例と同様とする。

リスト3

import java.io.*;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.SAXException;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;

public class Xml2PdfSample {

    public static void main(String[] args) {
    // FopFactoryオブジェクトの生成
    FopFactory fopFactory = FopFactory.newInstance();

        try {
            // 日本語フォントの設定の追加
            fopFactory.setUserConfig("[PATH_TO_FOP]/conf/fop.xconf");

            // 出力先の指定
            OutputStream output = new BufferedOutputStream(new FileOutputStream("fop-sample-ja.pdf"));

        // 出力フォーマットを指定してFopオブジェクトを生成
        Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, output);

        // XMLファイルの指定
        Source source = new StreamSource("fop-sample-ja.xml");

            // XSLTファイルの指定
            Source xsltSource = new StreamSource("xml2fo-sample.xsl");

        // 変換用オブジェクトの生成
        TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer(xsltSource);

        // 結果格納用のResultオブジェクトを生成
        Result result = new SAXResult(fop.getDefaultHandler());

        // 変換を実行
        transformer.transform(source, result);

            output.close();
        } catch (FOPException ex) {
            ex.printStackTrace();
        } catch (SAXException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (TransformerConfigurationException ex) {
            ex.printStackTrace();
        } catch (TransformerException ex) {
            ex.printStackTrace();
        }
    }
}

このプログラムをコンパイル/実行すると、図1のようなPDFファイルが生成される。

図1 Xml2PdfSampleによって出力されるPDFファイル

PDFを生成して返すServletを作る

続いて、XML文書をもとにPDF文書を生成してクライアントに返すようなServletを作ってみよう。とは言っても、基本的な変換の手順はこれまで紹介してきたやり方とまったく同じである。異なるのは出力先がローカルのファイルではなくリクエスト元のWebブラウザだという点だ。Servletの場合は、呼び出し時にdoGet()またはdoPost()メソッドにHttpServletRequestオブジェクトとHttpServletResponseオブジェクトが渡される。クライアントへのレスポンスはHttpServletResponseから取得したストリームに対して渡せばいい。つまり、PDFをクライアントに返すには、出力先をローカルのファイルストリームからHttpServletResponseの出力ストリームに変えるだけでいい。

注意点はContent-Typeを適切に設定する必要があるということだ。PDF形式のデータを返すので、下記のように「application/pdf」を指定すればよい。

リスト4

response.setContentType("application/pdf");

変換結果となるPDF文書の出力先を指定するには、Fopオブジェクト生成時に次のようにHttpServletResponseから取得した出力ストリームを渡すようにすればよい。

リスト5

OutputStream output = response.getOutputStream();
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, output);

その他の部分は通常のJavaプログラムの場合と同様である。リクエストをdoGet()メソッドで処理するものとした場合、全体のコードは次のようになる。

リスト6

package toolde;

import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.SAXException;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;

public class CreatePdfServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    // レスポンスの設定
    response.setContentType("application/pdf");
    OutputStream output = response.getOutputStream();

    // FopFactoryオブジェクトの生成
    FopFactory fopFactory = FopFactory.newInstance();

        try {
            // 日本語フォントの設定の追加
            fopFactory.setUserConfig("[PATH_TO_FOP]/conf/fop.xconf");

            // 出力フォーマットを指定してFopオブジェクトを生成
        Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, output);

        // XMLファイルの指定
        Source source = new StreamSource("fop-sample-ja.xml");

            // XSLTファイルの指定
            Source xsltSource = new StreamSource("xml2fo-sample.xsl");

        // 変換用オブジェクトの生成
        TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer(xsltSource);

        // 結果格納用のResultオブジェクトを生成
        Result result = new SAXResult(fop.getDefaultHandler());

        // 変換を実行
        transformer.transform(source, result);
        } catch (FOPException ex) {
            ex.printStackTrace();
        } catch (SAXException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (TransformerConfigurationException ex) {
            ex.printStackTrace();
        } catch (TransformerException ex) {
            ex.printStackTrace();
        } finally {
            output.close();
        }
    }
}

web.xmlは次のように記述した。

リスト7 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <servlet>
        <servlet-name>CreatePdfServlet</servlet-name>
        <servlet-class>toolde.CreatePdfServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>CreatePdfServlet</servlet-name>
        <url-pattern>/CreatePdfServlet</url-pattern>
    </servlet-mapping>
</web-app>

これをGlassFishやTomcatなどのServletコンテナにデプロイし、Webブラウザからアクセスすれば、図1と同じ内容のPDFファイルが返されるはずだ。

iTextとApache FOP

JavaでPDF文書を生成するライブラリとしてiTextとApache FOPの2つを紹介したが、iTextが独自のドキュメント構成によってPDFのコンテンツを保持するのに対し、Apache FOPはXSL-FOという標準規格に準拠している点が大きく異なっている。単にPDFを扱うだけであればiTextの方が直感的に分かりやすいが、XMLとの親和性という点ではApache FOPが大きく上回っている。またJAXPを利用すればJavaオブジェクトからXMLへの変換を行うこともできるので、(Javaオブジェクト)->(PDF)という変換についてもApache FOPだけで完結できるという利点もある。昨今では、帳票などの出力にPDFは欠かせない存在となってきている。JavaプログラムでPDFを扱う際には、これらのライブラリの利用を検討してみるといいだろう。