级别: 中级 Brian J Stewart, 首席顾问, Aqua Data Technologies, Inc.
2009 年 10 月 12 日 探究如何利用 XML 序列化和 XSL-FO 从 Java™ 业务对象生成动态 PDF 文档。通过 XSL-FO 样式表,您能够把数据呈现(视图)从数据和 Java 代码中分离出来,无需改动 Java 代码就能够修改 PDF 格式和布局。
许多业务应用程序都要求创建一个由储存在 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 业务对象生成不同的视图(输出文档)。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 用于本文的 Java 源代码 | xstream-code.zip | 20KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Brian J. Stewart 目前是 Aqua Data Technologies 的一位首席顾问。他是这家公司的创始人,该公司关注内容管理、XML 技术和企业客户端/服务器与 Web 系统。他架构并开发了基于 J2EE 和 .NET 平台的企业解决方案。 |
对本文的评价
|