許多業務應用程序都要求創建一個由儲存在 Java 業務對象中的數據組成的 PDF 文檔。您最好把這些 PDF 文檔看作是業務數據的視圖:這個視圖包含布局和結構,能夠輕松改變而且松散地與業務對象綁定在一起。本文使用 XML、XStream 和 Extensible Stylesheet Language Formatting Objects(XSL-FO)為這類普通業務問題提供了一個解決方案。通過介紹每個技術,為解決方案構建了基礎的框架,然後把這些技術整合到一個真實的示例中。
常用縮略語
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 的強大來自於其靈活性、簡單性、速度、低內存使用、低開銷,以及它對由庫生成的 XML 的控制。Xstream 庫的另外一個關鍵特征就是它對處理深度對象圖(如 CD 對象目錄,包含含有軌道信息的軌道)的支持。除非您想利用 XStream 的 Java 注釋支持,否則不需要對現有的業務對象進行任何修改。
使用 XSLT 轉換數據
下一個構建塊是 XSLT。XSLT 使您能夠把一個結構化的 XML 文檔轉換成各種各樣的輸出格式,如 XML 和 Html。XSLT 是一個復雜且健壯的基於 XML 的語言,包含幾個內置功能,如字符串功能和格式化功能。它還大量使用 XPath 進行查詢和選擇 XML 節點。
XML 轉換通過 XSLT 來完成。XSLT 樣式表定義了 XML 文檔所有的轉換規則。它由許多模板 組成,後者定義單個 XML 元素將如何轉換。您可以根據元素名稱、元素上下文(/Book/Title 不同於 /Catalog/Title)或者屬性(如 Title)(/Book/@Title)進行模板匹配。
使用 XSL-FO 格式化文檔
XSL-FO 僅僅是一個用於呈現的 XML 模式。它與 Html 和 CSS 類似,但與使用 CSS 文件具體化的樣式不一樣,它被存放在 FO 文檔中。利用 XML 序列化和 XSL-FO 方法有幾個好處:
XSL-FO 的替代方案
要使用 Java 技術生成 PDF 文檔,存在多個技術選擇。iText Java 庫是最常見的 XSL-FO 的替代方案。它提供了一個豐富的 API,可用來構建和操縱 PDF 文檔。
關注點分離。通過 XML 序列化和 XSL-FO 分離呈現(格式化和布局)和數據(Java 業務對象)。
數據和視圖的松耦合。負責調整格式和布局的 XSL-FO 樣式表對 Java 業務對象一無所知:它只知道業務對象的 XML 呈現。由於數據和呈現的分離,您可以在不修改基礎 Java 業務對象的情況下,輕松地改變輸出文檔的布局和格式。
呈現靈活性。XSL-FO 樣式表為布局和格式化提供了很大的靈活性。與 CSS 相似,XSL-FO 標准提供了普通的格式化元素,如字體、字號、字體粗細、填充,以及文本修飾。此外,XSL-FO 還提供:
屬性集。通過 XSLT 屬性集在一個文檔中共享格式化定義。
復雜的分頁支持。根據頁面情況(首頁、尾頁、其他)使用不同的布局。
腳注。為文檔添加注釋。
頁眉和頁腳。為頁面(首頁、尾頁、其他)定義頁眉和頁腳。
優化。設置 “keep with next” 和間距優化。
樣式繼承。繼承格式和樣式。
內容和書簽表。為 PDF 文檔生成一個目錄。
表格。支持包含表格頁眉、頁腳和正文的表格。
書寫模式。包括從左到右、從右到左、從上到下和從下到上各種選擇。
使用 XSL-FO 就是指結合 XSLT,把 SML 文檔轉換成 FO 文檔。然後 FO 文檔會經過 FO 引擎生成所需要的輸出(在這個用例中是 PDF 文檔)。圖 1 說明了 XSL-FO 的轉換流程。
圖 1. XSL-FO 轉換流程
解決方案技術架構
本文中的解決方案的主要目標是使用一個靈活的方法從 Java 業務對象生成 PDF 文檔。創建 PDF 視圖的技術組件應該與基礎的 Java 類完全隔離。由 XStream 生成並隨後轉換成 FO 文檔的臨時 XML 提供了隔離層。
圖 2 顯示如何結合構件 — Java 業務對象類、XStream 和 XSL-FO — 提供構建 Purchase Order PDF 文檔的端到端的解決方案。
圖 2. 解決方案架構
圖片看不清楚?請點擊這裡查看原圖(大圖)。
第一步,通過把包含 Java 類 IOrderItem 和 IAddress 的 IPurchaSEOrder(Purchase Order)業務對象序列化,構建 XML 文檔。為了提供最大的靈活性,使用 Java 注釋把類映射到 Purchase Order XML 文件中的對應元素和屬性。
第二步,把 Purchase Order XML 文檔轉換成能夠通過 FO 引擎(處理器)傳送的 FO 文檔。您可以通過 Purchase Order XSL-FO 樣式表來做到這一點。然後,生成的 FO 文檔通過 apache FOP — FO 引擎(處理器)的開源實現 — 傳送,生成 PDF 文檔。
目標是生成 圖 3 中的 PDF 文檔。您可以通過創建 XSL-FO 樣式表來做到這點。
圖 3. Purchase Order PDF 文檔
類圖
解決方案包括五個接口和它們的相應實現。表 1 展示了每個接口的簡單描述。
表 1. 接口和它們的實現
名稱 描述 IXMLSerializable 用於可序列化成 XML 的類的接口 IAddress 用於 Address 業務對象的接口 IPurchaSEOrder 用於 Purchase Order 業務對象的接口 IOrderItem 用於 Order Item 業務對象的接口 IPdfGenerator 用於把實現 IXMLSerializable 的任何業務對象轉換成 PDF 的生成器的接口。圖 4 顯示這些接口的 UnifIEd Modeling Language(UML)類圖。
圖 4. 表 1 中的接口的 UML 圖
圖片看不清楚?請點擊這裡查看原圖(大圖)。
這個解決方案由上述接口的實現和 Tester 類組成。圖 5 顯示了這些業務對象。
圖 5. 業務對象的 UML 圖
圖片看不清楚?請點擊這裡查看原圖(大圖)。
Purchase Order XML 模式
表 2 顯示由關鍵組件組成的 Purchase Order XML 模式。
表 2. Purchase Order 模式組件
關鍵元素 描述 OrderDate 包含采購訂單日期的字符串 CompanyAddress 包含公司的關鍵地址信息,包括公司名稱、街道地址、城市、州,以及郵政編碼 CustomerAddress 包含客戶的關鍵地址信息,包括公司名稱、街道地址、城市、州,以及郵政編碼 Items 包含訂購的所有項目,包括項目 ID、項目名稱和訂購數量使用 XStream 生成 Purchase Order XML 文件
生成 Purchase Order XML 文件的第一步是把 Purchase Order 類序列化成 XML。
這個解決方案使用注釋控制從 XStream 中生成 XML。在定制 XStream 生成的 XML 輸出時,注釋是首選方法;它們很容易使用,而且允許您在類級別定義 XML 映射規則,因此序列化代碼就變得不那麼脆弱了。不使用注釋的話,對業務對象類的任何修改,都需要同時修改序列化代碼。使用注釋後,由於映射規則在定義時並沒有脫離類和字段定義,因此代碼變得更清晰。在為一個類添加新的字段時,您可以在同一個 Java 源文件中定義映射規則。
注意:注釋只有在 Java 軟件開發包(JDK)1.5 和之後版本中才可用。此解決方案使用注釋控制 XML 輸出。
從 清單 1 中的 PurchaseOrder 類開始。PurchaseOrder 類實現 IPurchaSEOrder 和 IXmlSerializable 接口。在 IXmlSerializable 接口中定義的 toXml 方法在把采購訂單序列化成 XML 時起著關鍵作用。它包含調用 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 類。這些類不需要實現 IXMLSerializable 接口,這是因為它們不會被獨立於 PurchaSEOrder 類進行序列化。這些類包含在文章(請參見 下載)後面的源代碼中。它們非常直觀,由私有的實例變量和公開的 getters 和 setters 組成。私有的實例變量被注釋為用來控制 XStream 序列化。
調用 toXml 方法時,它將生成 清單 2 中的 XML。
清單 2. Purchase Order 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>
生成 Purchase Order FO 文檔
接下來,把 清單 2 中的 Purchase Order XML 文件轉換成 FO 文檔。當您編寫一個樣式表時,創建一個目標輸出文件的模型將會有所幫助。這個模型能夠幫助您編寫 XSLT 文件,對於 XSL-FO 開發尤其有幫助。在完成格式設置和布局之前,創建一個 FO 文檔並通過 apache FOP 運行它,確保輸出是正確的。完成布局設計後,創建一個 XSL-FO 樣式表,用來把源 XML 轉換成目標 FO 文檔。
創建 FO 文檔
FO 文檔由兩個主要部分組成:
頁面布局定義。這個部分描述了頁面布局,包括頁邊距和內容(例如,頁眉、頁腳和正文)。
頁面順序(內容/數據)。這個部分包含了頁眉、頁腳和正文的實際內容。每個頁面順序必須附加到頁面布局中。
從頁面布局模型開始。圖 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 區域為左邊和右邊呈現內容。
您可以在這些區域中添加兩種類型的內容:
靜態。靜態內容只限於靜態內容區域(例如,頁眉或者頁腳)。
流。流內容附加在一個特定的頁面布局中並且包含了能夠跨越多個頁面的內容。這是文檔的主要內容。
頁面布局 定義頁面尺寸和頁邊距。一個文檔可以有多個頁面布局。清單 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 支持幾個可以添加到靜態或流元素的內容元素:
塊。與 Html <DIV> 標記類似的內容容器(塊包含了文本或其他 FO 元素。)
內聯。與 Html <SPAN> 標記類似的內容容器
表。與 Html <TABLE> 標記類似
列表。與 Html <UL> 或 <OL> 標記類似的數據列表
外部圖形。一個圖形或圖像;與 Html <IMG> 標記類似
字符。一個單一字符;用來把特別的格式運用到各個字符中。
基本鏈接。文檔鏈接;與 Html <A> 標記類似
為 Purchase Order 構建樣式表
從處理根節點 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 元素中。模板創建了一個兩列的表,第一列包含公司徽標,而第二列包含公司地址。清單 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>
現在,可以顯示訂單信息了(也就是 Order ID 和 Order Date)。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/> 添加行編號,它可以生成序號。要計算采購項總計,把項目數量乘以項目費用。我提供了兩種計算訂單總數的方法 — 遞歸的已命名模板法和可替代的使用 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>
如前面所提到的,要生成訂單總數,有兩個主要的選擇。清單 8 顯示第一個 — 已命名的模板方法 —。calculateTotal 模板被遞歸地調用,直到不再剩下 Item 元素為止。內聯注釋描述模板的運行方式。
清單 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>
第二個選擇是使用各種模式的遞歸模板。清單 9 顯示用於計算總數的模板。當模式為 calculateTotal 時,它適用於所有的 Item 元素。在應用使用該模式的模板期間,需要為任何被忽略的子節點創建一個空模板。與已命名的模板類似,模板被遞歸地應用,直到無法再找到 Item 元素為止。內聯注釋描述了模板的運行方式 。
清單 9. 項目模板(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。
最後一步是使用您剛剛創建好的 XSL 樣式表從 XStream 生成的 XML 中創建 PDF。PdfGenerator 類 負責生成 PDF,應用使用標准的 Transformer 類的樣式表,並通過 apache FO 傳送文檔。
清單 10. 項目模板(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 在體系(set)處理方面考慮周到,而不是使用程序化編程。
需要時使用遞歸。為了避免遞歸模板,樣式表通常做得更為復雜。
全盤考慮 XML 模式。模式通常都是倉促建立的,而且模式設計很難完善和維護。需要仔細考慮是使用屬性還是使用元素。例如,如果您把 Publisher 定義為屬性而不是 XML 元素,那麼很難把與出版者有關的附加信息添加到 XML 文檔。
結束語
在本文中,您了解了如何使用 XStream 和 XSL-FO 輕松地從 Java 業務對象中創建 PDF 文檔。分離關注點使您能夠把視圖和業務對象分離開來,因此您能夠改變視圖(PDF 文檔)而不必修改 Java 代碼。您可以通過創建根據惟一的呈現需求定制的 XSLT 樣式表來做到這點。再有,可以使用不同的 XSLT 樣式表為同一個 Java 業務對象生成不同的視圖(輸出文檔)。
本文示例源代碼或素材下載