レベル: 中級 Brian J Stewart, Principal Consultant, Aqua Data Technologies, Inc.
2009年 9月 01日 XML シリアライズと XSL-FO を使って Java™ ビジネス・オブジェクトから動的な PDF 文書を生成する方法を学びましょう。XSL-FO スタイルシートを使用すると、データの表示 (ビュー) をデータや Java コードから分離することができ、Java コードを変更せずに PDF のフォーマットやレイアウトを変更することができます。
多くのビジネス・アプリケーションでは、Java のビジネス・オブジェクトに保存されたデータで構成される PDF 文書を作成する必要があります。これらの PDF 文書については、Java のビジネス・オブジェクトのビューとみなすのが適切です。このビューは、レイアウトや構造を含め、容易に変更できる必要があり、またビューとビジネス・オブジェクトは疎結合でなければなりません。この記事では、こうしたビジネス・アプリケーションの一般的な要求を満たすソリューションとして、XML と XStream、そして XSL-FO (Extensible Stylesheet Language Formatting Objects) を使ったソリューションを紹介します。最初にソリューションの基礎となる各技術を紹介し、次に各技術を統合して実際の例に応用します。
 | よく使われる頭字語
- API: Application Program Interface
- CD: Compact Disc
- CSS: Cascading Stylesheet
- HTML: HyperText Markup Language
- PDF: Portable Document Format
- XML: Extensible Markup Language
- XSL: Extensible Stylesheet Language
- XSLT: Extensible Stylesheet Language Transformation
- W3C: World Wide Web Consortium
|
|
まず、この記事で使用する各技術を説明します。
XStream を使ってデータをシリアライズする
XStream はオブジェクトと XML との間でシリアライズとデシリアライズを行うための、単純でありながら強力なライブラリーです。XStream の強力さは、その柔軟性、単純さ、速さ、少ないメモリー使用量、小さいオーバーヘッド、そして XStream ライブラリーによって生成される XML を制御できる点にあります。XStream ライブラリーのもう 1 つの重要な特徴として、深いオブジェクト・グラフ (例えばトラック情報を含むトラックを格納した CD オブジェクトのカタログなど) を処理することができます。XStream では既存のビジネス・オブジェクトを何も変更する必要がありませんが、XStream による Java アノテーションのサポートを利用する場合は例外です。
XSLT を使ってデータを変換する
次の構成ブロックは XSLT です。XSLT を利用すると、構造化された XML 文書をさまざまな出力フォーマット (XML や HTML) に変換することができます。XSLT は XML ベースの複雑で堅牢な言語であり、いくつかの関数が組み込まれています (例えばストリング関数やフォーマット設定関数など)。また XSLT では XML ノードの照会や選択には XPath が使われます。
XML の変換は XSLT によって行われます。XSLT スタイルシートは 1 つのXML 文書に対するすべての変換ルールを定義します。XSLT スタイルシートはテンプレートで構成され、テンプレートが個々の XML 要素の変換方法を定義しています。適切なテンプレートの選択は、要素名、要素のコンテキスト (/Book/Title と /Catalog/Title は異なります)、あるいは属性 (Title (/Book/@Title) など) に基づいて行います。
XSL-FO を使って文書のフォーマットを設定する
XSL-FO は表示のための XML スキーマにすぎません。XSL-FO は HTML や CSS に似ていますが、CSS ファイルではスタイル情報が外部化されているのに対し、XSL-FO の場合には FO 文書の中にスタイル情報が含まれています。XML シリアライズと XSL-FO による手法を活用すると、以下のようにいくつかのメリットがあります。
 | XSL-FO に代わるもの
Java 技術を使って PDF 文書を生成するための技術的な選択肢はいくつかあります。XSL-FO に代わる最も一般的な選択肢は、PDF 文書の作成や操作のためのリッチな API を持つ iText Java ライブラリーです。
|
|
- 関心の分離。XML シリアライズと XSL-FO によって、表示 (フォーマット設定とレイアウト) はデータ (Java ビジネス・オブジェクト) から分離されます。
- データとビューの疎結合。フォーマットやレイアウトの設定を行う XSL-FO スタイルシートは Java ビジネス・オブジェクトに関する情報は何も持っておらず、ビジネス・オブジェクトを XML で表現したものを認識しているにすぎません。おおもとの Java ビジネス・オブジェクトを何も変更しなくても、出力文書のレイアウトとフォーマットの設定を容易に変更することができます。これはデータと表示が分離されているためです。
- 表示の柔軟性。XSL-FO スタイルシートはレイアウトとフォーマットに関して非常に柔軟性があります。CSS と同様、XSL-FO 標準にも、一般的なフォーマット設定要素が用意されています (フォント、フォント・サイズ、フォントの太さ、パディング、テキスト装飾など)。これらの他にも、XSL-FO には以下の要素が用意されています。
- 属性セット。XSLT の属性セットによって文書内でフォーマット設定の定義を共有することができます。
- 複雑なページネーションのサポート。ページの条件 (先頭、最終、その他) に基づいて、さまざまなレイアウトを使用することができます。
- 脚注。文書に脚注を追加することができます。
- ヘッダーとフッター。ページ (先頭、最終、その他) に対するヘッダーとフッターを定義することができます。
- 最適化。keep-with-next の設定をして、空白を最適にすることができます。
- スタイルの継承。フォーマット設定とスタイルを継承することができます。
- 目次とブックマーク。PDF 文書の目次を生成することができます。
- 表。表の標題、フッター、本体などを含めて、表をサポートしています。
- ライティング・モード (文字表記の方向)。この選択肢としては、左から右、右から左、上から下、下から上があります。
XSL-FO を使う場合には、XML 文書に XSLT を組み合わせて FO 文書に変換すればよいだけです。すると FO 文書は FO エンジンにパイプされ、必要な出力 (この場合は PDF 文書) が生成されます。図 1は XSL-FO による変換プロセスを示しています。
図 1. XSL-FO による変換プロセス
ソリューションの技術アーキテクチャー
この記事で説明するソリューションの最大の目標は、任意の Java ビジネス・オブジェクトから柔軟な方法で PDF 文書を生成することです。PDF ビューを生成する技術コンポーネントは、おおもとの Java クラスから分離されている必要があります。XStream によって生成される中間的な XML は続いて FO 文書に変換されますが、この中間的な XML によってレイヤー間が分離されます。
図 2 は、これらの構成ブロック (Java ビジネス・オブジェクト・クラス、XStream、XSL-FO) を組み合わせ、包括的なソリューションとして、注文書 (Purchase Order) の PDF 文書を作成する方法を示しています。
図 2. ソリューションのアーキテクチャー
最初のステップとして、IOrderItem と IAddress という Java クラスで構成される IPurchaseOrder (注文書) ビジネス・オブジェクトをシリアライズすることで、XML 文書を作成します。柔軟性を最大にするために、Java アノテーションを使用して、これらのクラスを注文書の XML ファイルの対応する要素と属性にマッピングします。
第 2 のステップでは、注文書の XML ファイルを FO 文書に変換し、FO エンジン (プロセッサー) にパイプできるようにします。そのためには注文書の XSL-FO スタイルシートを使います。結果として生成された FO 文書は Apache FOP (FO エンジン (プロセッサー) のオープンソース実装) にパイプされ、PDF 文書が作成されます。
目標は図 3 の PDF 文書を作成することです。そのためには XSL-FO スタイルシートを作成します。
図 3. 注文書の PDF 文書
クラス図
このソリューションは、5 つのインターフェースと、それぞれのインターフェースに対応する実装で構成されています。表 1 は各インターフェースの簡単な説明です。
表 1. インターフェースとその実装
| 名前 | 説明 |
|---|
IXmlSerializable | XML にシリアライズされるクラスのためのインターフェース | IAddress | 住所を表すビジネス・オブジェクトのためのインターフェース | IPurchaseOrder | 注文書を表すビジネス・オブジェクトのためのインターフェース | IOrderItem | 注文品目を表すビジネス・オブジェクトのためのインターフェース | IPdfGenerator | IXmlSerializable を実装する任意のビジネス・オブジェクトを PDF に変換する、PDF 生成プログラムのためのインターフェース
|
図 4 は、これらのインターフェースの UML (Unified Modeling Language) クラス図を示しています。
図 4. 表 1 のインターフェースの UML クラス図
このソリューションは上記のインターフェースの実装と Tester クラスで構成されています。これらのビジネス・オブジェクトを図 5 に示します。
図 5. ビジネス・オブジェクトの UML 図
注文書の XML スキーマ
表 2 は、注文書の XML スキーマを構成する重要なコンポーネントを示しています。
表 2. 注文書のスキーマのコンポーネント
| 重要な要素 | 説明 |
|---|
OrderDate | 注文書の日付を含むストリング | CompanyAddress | 会社名、番地・ストリート、市、州、郵便番号など、その会社に関する重要な住所情報を含んでいます | CustomerAddress | 会社名、番地・ストリート、市、州、郵便番号など、その顧客に関する重要な住所情報を含んでいます | Items | 品目の ID、品目の名前、注文数などを含め、注文されたすべての品目を含んでいます |
XStream を使って注文書の XML ファイルを生成する
注文書の XML ファイルを生成するための第 1 のステップは、注文書のクラスを XML にシリアライズすることです。
このソリューションでは、XStream を使って XML を生成する動作の制御にアノテーションを使用します。アノテーションを使用する方法は、XStream によって生成される XML 出力をカスタマイズするための推奨の方法です。アノテーションは使い方が簡単であり、XML へのマッピング・ルールをクラス・レベルで定義できるため、シリアライズするためのコードがあまり複雑なものになりません。アノテーションを使わない場合には、ビジネス・オブジェクト・クラスを変更するとシリアライズ・コードも必ず変更しなければなりません。アノテーションを使うと、クラスやフィールドの定義とマッピング・ルールの定義が別のところでされることがなくなるため、コードがすっきりとします。クラスに新しいフィールドを追加する場合には、同じ Java ソース・ファイルの中でマッピング・ルールを定義することができます。
注: アノテーションは JDK (Java software development kit) バージョン 1.5 およびそれ以降でないと使用することができません。このソリューションではアノテーションを使って XML 出力を制御しています。
まず PurchaseOrder クラスから始めます (リスト 1)。PurchaseOrder クラスは IPurchaseOrder インターフェースと IXmlSerializable インターフェースを実装しています。IXmlSerializable インターフェースで定義される toXml メソッドは注文書を XML にシリアライズする上で非常に重要です。toXml メソッドは XStream によるシリアライズを実行するために必要な最低限のコード行のみを含んでおり、アノテーションの自動検出が有効になっています。クラスの中に含まれているクラスを XStream が見つけると、XStream はアノテーションを探し、そのクラスをシリアライズする方法を判断します。クラスの中にアノテーションが見つからない場合には、XStream はデフォルトの方法を使います。
リスト 1. PurchaseOrder
class@XStreamAlias("PurchaseOrder")
public class PurchaseOrder implements IPurchaseOrder, IXmlSerializable {
// private instance variables
/** The m_orderId. */
@XStreamAlias("OrderId")
@XStreamAsAttribute
private String m_orderId = null;
/** The m_order date. */
@XStreamAlias("OrderDate")
private Date m_orderDate = null;
/** The m_company addr. */
@XStreamAlias("CompanyAddress")
private IAddress m_companyAddr = null;
/** The m_customerAdress addr. */
@XStreamAlias("CustomerAddress")
private IAddress m_customerAdress = null;
/** The m_items. */
@XStreamAlias("Items")
private ArrayList<IOrderItem> m_items = null;
...
// IXmlSerializable method
public String toXml() {
XStream xstream = new XStream();
xstream.autodetectAnnotations(true);
return xstream.toXML(this);
}
}
|
@XStreamAlias アノテーションは XStream に対して、どの要素名と属性名をシリアライズとデシリアライズに使うのかを指示します。XStreamAsAttribute アノテーションは、そのフィールドを要素としてではなく属性としてシリアライズする必要があることを示しています。
次のステップは Address クラスと OrderItem クラスを作成することです。これらのクラスは PurchaseOrder クラスとは別々にシリアライズされることはないため、IXmlSerializable インターフェースを実装する必要はありません。これらのクラスは、この記事に付属するソース・コードの中に含まれています (「ダウンロード」を参照)。これらのクラスは非常に単純であり、private なインスタンス変数と public なゲッターとセッターで構成されています。private なインスタンス変数には、XStream によるシリアライズを制御するためのアノテーションが追加されています。
toXml メソッドが呼び出されると、リスト 2 の XML が作成されます。
リスト 2. 注文書の XML
<PurchaseOrder OrderId="PO-123-456789">
<OrderDate>2009-06-14 13:05:02.251 EDT</OrderDate>
<CompanyAddress>
<CompanyName>ACME Company</CompanyName>
<StreetAddress>123 Main Street</StreetAddress>
<City>Orlando</City>
<State>FL</State>
<ZipCode>32801</ZipCode>
</CompanyAddress>
<CustomerAddress>
<CompanyName>A++</CompanyName>
<StreetAddress>123 8th Avenue</StreetAddress>
<City>Orlando</City>
<State>FL</State>
<ZipCode>32801</ZipCode>
</CustomerAddress>
<Items>
<Item ItemId="A1B2C3" ItemName="Widget" Quantity="100" ItemCost="100.5"/>
<Item ItemId="C3B2A1" ItemName="Micro-Widget" Quantity="1000" ItemCost="10.75"/>
</Items>
</PurchaseOrder>
|
注文書の FO 文書を生成する
次に、リスト 2 の注文書の XML ファイルを FO 文書に変換します。スタイルシートを作成する際、ターゲット出力ファイルのモックアップを作成すると便利です。モックアップを利用すると XSLT ファイルを作成することができます。XSL-FO を作成する場合には特にモックアップが便利です。フォーマット設定とレイアウトを決定するためには、FO 文書を作成し、その FO 文書を Apache FOP で実行し、出力が適切であることを確認します。レイアウトが決定できると、あとはソースの XML をターゲットの FO 文書に変換する XSL-FO スタイルシートを作成すればよいだけです。
FO 文書を作成する
FO 文書は下記の 2 つの主な部分から構成されています。
- ページ・レイアウト定義。この部分には、ページのレイアウト (マージンの設定やコンテンツ領域 (例えばヘッダー、フッター、本文) の配置など) に関する記述をします。
- ページ・シーケンス (コンテンツ/データ)。この部分には、ヘッダー、フッター、本体の実際のコンテンツが含まれます。各ページ・シーケンスは必ずページ・レイアウトに付属していなければなりません。
まずページ・レイアウト・モデルから始めます。図 6 は FO ページの主なコンポーネントである、Region-Before、Region-After、Region-Body、Region-Start、Region-End を示しています。
図 6. FO ページ・レイアウト・モデル
図 6 で、Region-Body はメインのコンテンツ領域です。Region-Before と Region-After は通常、ヘッダーとフッターに使われます。同様に、左の Region-Start 領域と右の Region-End 領域にコンテンツを配置することもできます。
これらの領域に追加できるコンテンツのタイプには下記の 2 つがあります。
- 静的。静的なコンテンツは静的なコンテンツ領域に限定されます (例えばヘッダーやフッターなど)
- フロー。フロー・コンテンツは具体的なページ・レイアウトが設定されたページのコンテンツで、複数のページにまたがることができます。これが文書のメイン・コンテンツになります。
ページ・レイアウトはページのサイズとマージンを定義します。1 つの文書に複数のページ・レイアウトを持つことができます。リスト 3 は基本的な 8.5 x 11 インチのページを定義しています。
リスト 3. ページ・レイアウトの例
<fo:layout-master-set>
<fo:simple-page-master
margin-right="1in"
margin-left="1in"
margin-bottom="0.5in"
margin-top="1in"
page-width="8.5in"
page-height="11in"
master-name="standardletter">
<fo:region-body
margin-bottom="1in"
margin-top="1in"/>
<fo:region-after extent="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
|
FO は静的要素またはフロー要素に追加できるコンテンツ要素をいくつかサポートしています。
- block: HTML の
<DIV> タグに似た、コンテンツのコンテナー (block はテキストまたは他の FO 要素を含むことができます)
- inline: HTML の
<SPAN> タグに似た、コンテンツのコンテナー
- table: HTML の
<TABLE> タグに似ています
- list: HTML の
<UL> タグや <OL> タグに似たデータ・リスト
- external-graphic: HTML の
<IMG> タグに似た、グラフィックまたは画像
- character:1 つの文字 (個々の文字に特別なフォーマットを適用するために使われます)
- basic-link: HTML の
<A> タグに似た、文書のリンク
注文書のためのスタイルシートを作成する
まずルート・ノード PurchaseOrder を処理し、layout-master-set 要素と page-sequence 要素を作成します。リスト 4 は PurchaseOrder 要素のためのテンプレートを示しています。
リスト 4. PurchaseOrder のテンプレート
<xsl:template match="PurchaseOrder">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<!-- Create page layout - including margins -->
<fo:layout-master-set>
<!-- Page size and margins -->
<fo:simple-page-master
master-name="all"
page-height="11in"
page-width="8.5in"
margin-top="0.25in"
margin-bottom="0.25in"
margin-left="1in"
margin-right="1in">
<!-- Main content layout -->
<fo:region-body margin-top="2in"
margin-bottom="1in" />
<!-- Header layout -->
<fo:region-before extent="2in" />
<!-- Footer layout -->
<fo:region-after extent="1in" />
</fo:simple-page-master>
</fo:layout-master-set>
<!-- Create page sequence-->
<fo:page-sequence master-reference="all">
<!-- Create header -->
<fo:static-content flow-name="xsl-region-before">
<!-- Output company address information in header -->
<xsl:apply-templates
select="CompanyAddress" />
<fo:block font-size="18pt"
font-family="sans-serif"
line-height="1.5em"
background-color="black"
color="white"
text-align="center"
>PURCHASE ORDER</fo:block>
</fo:static-content>
<!-- Create footer -->
<fo:static-content flow-name="xsl-region-after">
<!-- Add page number to footer -->
<fo:block text-align="end"
font-size="10pt"
font-family="serif">
Page <fo:page-number />
</fo:block>
</fo:static-content>
<!-- Create main document content -->
<fo:flow flow-name="xsl-region-body">
<!-- Display order information (date, id)-->
<xsl:call-template name=
"DisplayOrderInformation" />
<!-- Display Customer Address information -->
<xsl:apply-templates select=
"CustomerAddress" />
<!-- Display items-->
<xsl:apply-templates select="Items" />
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
|
次のステップとして、会社のロゴと会社の住所で構成されるヘッダーを作成します。Region-Before の静的コンテンツ・フローの中に、すべての CompanyAddress 要素にテンプレートを適用するための呼び出しがあります。このテンプレートによって、2 つの列を持つ表が作成されます。最初の列には会社のロゴが含まれ、2 番目の列には会社の住所が含まれます。リスト 5 は CompanyAddress 要素に対するテンプレートを示しています。
リスト 5. CompanyAddress テンプレート
<xsl:template match="CompanyAddress">
<fo:table width="100%">
<fo:table-column column-width="40%" />
<fo:table-column column-width="60%" />
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block>
<fo:external-graphic
src="url(D:\workspace\DwArticle5\resources\CompanyLogo.jpg)" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<fo:block text-align="right">
<xsl:value-of select="./StreetAddress" />
</fo:block>
<fo:block text-align="right">
<xsl:value-of select="concat(./City, ' ',
./State, ' ',
./ZipCode)" />
</fo:block>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:template>
|
今度は、注文情報 (つまり、注文番号と注文日) を表示します。DisplayOrderInformation テンプレートに名前を付けて呼び出すためには下記のようにします。
<xsl:call-template name="DisplayOrderInformation" />
|
従来、テンプレートはすべて、<xsl:apply-templates/> を使って呼び出されていました。名前付きテンプレートは、値を返す手続き型のルーチンや、すべての子ノードのテンプレートを処理せずに子ノードからデータを抽出したい場合に便利です。また名前付きテンプレートは再帰型のシナリオにも便利です。ここでは名前付きテンプレートを使って例として注文情報を表示しますが、より適切な他の手法もあります。リスト 6 は名前付きテンプレートを示しています。
リスト 6. DisplayOrderInformation テンプレート
<xsl:template name="DisplayOrderInformation">
<fo:block text-align="right">
<fo:inline font-weight="bold">Id:</fo:inline>
<xsl:value-of select="./@OrderId" />
</fo:block>
<fo:block text-align="right">
<fo:inline font-weight="bold">Order Date:</fo:inline>
<xsl:value-of select="./OrderDate" />
</fo:block>
</xsl:template>
|
Items 要素と Item 要素を変換し、各 Item に対する行を含む表にします。各 Item に行品目番号を追加し、また行品目の合計と注文の合計も追加します。行番号を追加するためには、連続した番号を生成する <xsl:number/> を使用します。行品目の合計を計算するためには、品目の数量と品目の単価を掛け算します。ここでは注文の合計の計算用として 2 つの方法を紹介します。1 つは名前付きテンプレートを再帰的に使用する方法、もう 1 つは XSL テンプレートのモードと再帰を使用する方法です。合計を計算するためには、各品目の合計数量と各品目の単価を掛け算します。
まず、品目を含む表を描画します。リスト 7 は関連するすべてのテンプレートを示しています・
リスト 7. Items テンプレートと Item テンプレート
<!-- Template for Items element-->
<!-- Outputs a TABLE -->
<xsl:template match="Items">
<fo:block font-weight="bold" background-color="black" color="white"
padding="2pt">ITEMS</fo:block>
<fo:table width="100%">
<!-- Add table column widths -->
<fo:table-column column-width="10%" />
<fo:table-column column-width="15%" />
<fo:table-column column-width="30%" />
<fo:table-column column-width="15%" />
<fo:table-column column-width="15%" />
<fo:table-column column-width="15%" />
<!-- Add table header row -->
<fo:table-header>
<fo:table-row>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>#</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Item ID</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Description</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Quantity</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Item Cost</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Total Cost</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:apply-templates />
<!-- Add row for summary. Span all columns and apply bold to total-->
<fo:table-row>
<fo:table-cell border="solid black 1px"
number-columns-spanned="5">
<fo:block font-weight="bold"
text-align="right">Total
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold">
<!-- Return the number into a variable which is displayed below -->
<xsl:variable name="total">
<!-- The following line needs to be uncommented if the mode
and recursion approach is utilized. -->
<!--
<xsl:apply-templates select="Item[1]"
mode="calculateTotal" />-->
<!-- The following call-template needs to be commented out if the call-template
approach is not utilized. -->
<xsl:call-template name="calculateTotal">
<xsl:with-param name="nodes" select="Item" />
</xsl:call-template>
</xsl:variable>
<!-- Output value of "total" variable -->
<xsl:value-of select="format-number($total, '$#,##0.00')"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:template>
<!-- Template for Item element-->
<!-- Outputs a TR for each Item -->
<xsl:template match="Item">
<fo:table-row>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:number/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="@ItemId" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="@ItemName" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="@Quantity" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="format-number(@ItemCost, '$#,##0.00')" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<xsl:variable name="total" select="@Quantity * @ItemCost" />
<fo:block>
<xsl:value-of select="format-number($total, '$#,##0.00')" />
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
|
先ほど説明したように、注文の合計を生成するための主な選択肢は 2 つあります。リスト 8 は最初の方法、つまり名前付きテンプレートを使用する方法を示しています。残りの Item 要素が見つからなくなるまで、calculateTotal テンプレートが再帰的に呼び出されます。インラインのコメントは、このテンプレートの動作を説明しています。
リスト 8. 名前付きテンプレートを使って計算する
<xsl:template name="calculateTotal">
<xsl:param name="nodes" select="/.." />
<xsl:param name="subtotal" select="0" />
<xsl:variable name="total"
select="$subtotal + ($nodes[1]/@ItemCost * $nodes[1]/@Quantity)" />
<xsl:choose>
<!-- check if more Item nodes to process and stop and return value if not -->
<xsl:when test="not($nodes[2])">
<xsl:value-of select="$total" />
</xsl:when>
<!-- recursively call template since there are more Item nodes to process -->
<xsl:otherwise>
<xsl:call-template name="calculateTotal">
<xsl:with-param name="nodes"
select="$nodes[position() > 1]" />
<xsl:with-param name="subtotal" select="$total" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
|
2 番目の選択肢はモードを使ったテンプレートを再帰的に使用する方法です。リスト 9 は合計を計算するためのテンプレートを示しています。モードが calculateTotal の場合、すべての Item 要素にこのテンプレートが適用されます。モードを使うテンプレートを適用する際に無視する必要がある子ノードに対して、空のテンプレートを作成する必要があります。名前付きテンプレートの場合と同様、Item 要素が見つからなくなるまで、このテンプレートが再帰的に適用されます。インラインのコメントは、このテンプレートの動作を説明しています。
リスト 9. Item テンプレート (calculateTotal モード)
<xsl:template match="Item" mode="calculateTotal">
<xsl:param name="subtotal" select="0" />
<xsl:variable name="total"
select="$subtotal + (@ItemCost * @Quantity)" />
<xsl:choose>
<!-- check if more Item nodes to process and stop and return value if not -->
<xsl:when test="not(following-sibling::Item)">
<xsl:value-of select="$total" />
</xsl:when>
<xsl:otherwise>
<!-- recursively apply template since there are more Item nodes to process -->
<xsl:apply-templates select="following-sibling::Item[1]"
mode="calculateTotal">
<xsl:with-param name="subtotal" select="$total" />
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
|
PDF 文書を生成する
 | Apache FOP に代わるもの
FO エンジンには商用のものがいくつかあり、最もよく使われるのは RenderX XEP、Antenna House XSL Formatter、PTC Arbortext Publishing Engine の 3 つです。これらへのリンクは、「参考文献」を参照してください。
|
|
最後のステップとして、XStream によって生成された XML から、上記で作成した XSL スタイルシートを使って PDF を作成します。PDF を生成する PdfGenerator クラスは標準的な Transformer クラスを使ってスタイルシートを適用し、変換された文書を Apache FOP にパイプします。
リスト 10. Item テンプレート (calculateTotal モード)
public class PdfGenerator implements IPdfGenerator {
public OutputStream generate(IXmlSerializable object, String stylesheetPath,
OutputStream pdfContent) {
try {
// setup xml input source
String xml = object.toXml();
StreamSource xmlSource =
new StreamSource(new ByteArrayInputStream(xml.getBytes()));
// setup xsl stylesheet source
File xslFile = new File(stylesheetPath);
FileInputStream xslFileStream = new FileInputStream(xslFile);
StreamSource xslSource = new StreamSource(xslFileStream);
// get transformer
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer transformer = tfactory.newTransformer(xslSource);
// setup FOP
FopFactory fopFactory = FopFactory.newInstance();
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
foUserAgent.setProducer(this.getClass().getName());
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent,
pdfContent);
// perform transformation
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(xmlSource, res);
} catch (FileNotFoundException e) { e.printStackTrace();
} catch (TransformerException e) { e.printStackTrace();
} catch (FOPException e) { e.printStackTrace();
}
return pdfContent;
}
}
|

 |
XStream と XSL-FO を使用する際の推奨事項
以下に記載するのは、XStream と XSL-FO によるソリューションに関する推奨事項です。
- XStream を構成するためには Java のアノテーションを使用してください。アノテーションによって非常に柔軟性が高くなり、また結合が疎になるからです。
- 名前付きテンプレートを呼び出すのではなく、モードを指定したテンプレートを使用してください。XSLT は固定的な処理を第一に考えて設計されており、手続き型プログラミングは考慮されていないからです。
- 必要に応じて再帰を使用してください。再帰型のテンプレートを避けようとすると、スタイルシートがより複雑になってしまうことが非常によくあります。
- XML スキーマを十分に考慮してください。スキーマを短時間で作り上げた結果、スキーマの拡張や保守が困難な設計になってしまうことが非常によくあります。属性を使うのか、あるいは要素を使うのか、十分に注意して検討する必要があります。例えば、
Publisher を XML 要素ではなく属性として定義すると、発行者に関する情報を XML 文書に追加しにくくなります。
まとめ
この記事では、XStream と XSL-FO を使って Java ビジネス・オブジェクトから容易に PDF 文書を作成できることを学びました。関心を分離することによってビューがビジネス・オブジェクトから分離されるため、ビュー (PDF 文書) を変更しても Java コードを変更する必要がありません。こうしたことを実現するためには、文書に特有の表示要件に合わせて調整された XSLT スタイルシートを作成すればよいだけです。また、異なる XSLT スタイルシートを使うことで、同じ Java ビジネス・オブジェクトの異なるビュー (出力文書) を生成することもできます。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Java source code for this article | xstream-code.zip | 20KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
- Java クラスと XML との間でシリアライズ、デシリアライズを行うための API、XStream ライブラリーをダウンロードしてください。
- FO 文書から PDF を生成するための API、Apache FOP をダウンロードしてください。
- よく使われる商用の FO エンジンとして、下記の 3 つについて調べてください。
- IBM 製品の試用版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。
議論するために
著者について  | 
|  | Brian J. Stewart は現在、Aqua Data Technologies で主任コンサルタントを務めています。彼が設立したこの会社は、コンテンツ・マネージメント、XML 技術、そしてエンタープライズ・クライアント/サーバーおよび Web システムを専門としています。彼は Java EE および Microsoft .NET プラットフォームをベースとしてエンタープライズ・ソリューションを設計、開発しています。Brian は BrianJStewart.com でブログを公開しています。 |
記事の評価
|