跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

使用 XStream 和 XSL-FO 生成 PDF

利用 XStream 和 XSL-FO 生成动态文档

Brian J Stewart, 首席顾问, Aqua Data Technologies, Inc.
Brian J. Stewart 目前是 Aqua Data Technologies 的一位首席顾问。他是这家公司的创始人,该公司关注内容管理、XML 技术和企业客户端/服务器与 Web 系统。他架构并开发了基于 J2EE 和 .NET 平台的企业解决方案。

简介: 探究如何利用 XML 序列化和 XSL-FO 从 Java™ 业务对象生成动态 PDF 文档。通过 XSL-FO 样式表,您能够把数据呈现(视图)从数据和 Java 代码中分离出来,无需改动 Java 代码就能够修改 PDF 格式和布局。

发布日期: 2009 年 10 月 12 日
级别: 中级 其他语言版本: 英文
访问情况 : 1569 次浏览
评论: 


许多业务应用程序都要求创建一个由储存在 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 转换流程
XML 文档的 XSL-FO 转换流程(先生成一个 FO 文档并最终转换成 PDF 文档)

解决方案技术架构

本文中的解决方案的主要目标是使用一个灵活的方法从 Java 业务对象生成 PDF 文档。创建 PDF 视图的技术组件应该与基础的 Java 类完全隔离。由 XStream 生成并随后转换成 FO 文档的临时 XML 提供了隔离层。

图 2 显示如何结合构件 — Java 业务对象类、XStream 和 XSL-FO — 提供构建 Purchase Order PDF 文档的端到端的解决方案。


图 2. 解决方案架构
生成 XML 文档然后转换成 PDF 文档的解决方案架构

第一步,通过把包含 Java 类 IOrderItemIAddressIPurchaseOrder(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 文档
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 图
图 4. 表 1 中的接口的 UML 图

这个解决方案由上述接口的实现和 Tester 类组成。图 5 显示了这些业务对象。


图 5. 业务对象的 UML 图
业务对象的 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 类实现 IPurchaseOrderIXmlSerializable 接口。在 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 注释指示了字段应该被作为属性而非元素进行序列化。

下一步是创建 AddressOrderItem 类。这些类不需要实现 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 页面布局模型
FO 页面布局模型

图 6 中,Region-Body 是主要的内容区域。Region-BeforeRegion-After 通常用于页眉和页脚。类似地,您可以通过 Region-StartRegion-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-setpage-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>

ItemsItem 元素转换成一个表格,其中每个 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.zip20KBHTTP

关于下载方法的信息


参考资料

学习

  • W3C XSL-FO Tutorial:请阅读 XSL-FO 的详细指南,并且学习如何格式化 XML 数据,从而把它们输出到屏幕、纸张和其他媒介。

  • W3C XSL 版本 1.1:请阅读 XSL-FO 的 W3C 规范。探讨用来表述样式表的 XSL 功能和语法。

  • 使用 XStream 把 Java 对象序列化为 XML(Rajiv Bangalore,developerWorks,2009 年 7 月):学习如何使用 XStream 序列化 Java 对象。

  • IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 及相关技术的开发人员。

  • XML 技术库:developerWorks XML 专区提供了大量技术文章和技巧、教程、标准以及 IBM 红皮书。

  • developerWorks 技术活动网络广播:随时关注技术的最新进展。

  • 技术书店:浏览关于这些主题和其他技术主题的图书。

  • 要收听针对软件开发人员的有趣访谈和讨论,请查看 developerWorks 播客

获得产品和技术

讨论

关于作者

Brian J. Stewart 目前是 Aqua Data Technologies 的一位首席顾问。他是这家公司的创始人,该公司关注内容管理、XML 技术和企业客户端/服务器与 Web 系统。他架构并开发了基于 J2EE 和 .NET 平台的企业解决方案。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=434967
ArticleTitle=使用 XStream 和 XSL-FO 生成 PDF
publish-date=10122009
author1-email=BrianJStewart_cnnew1@AquaDataTech.com
author1-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。