65.9K
CodeProject 正在变化。 阅读更多。
Home

从 XML 创建 PDF 文档

starIconstarIconstarIconstarIconstarIcon

5.00/5 (24投票s)

2013年7月30日

CPOL

3分钟阅读

viewsIcon

247001

downloadIcon

5819

从 XML 创建 PDF 文档。

引言

本文介绍如何使用 XML/XSL-FO 和开源库 FO.NET,通过混合输入参数、图像和静态本地化 string 来生成 PDF 文档。我认为这种方法非常适合自动化生成发票或与 Web 服务/服务器页面输入参数相关的简单动态文档/报告的过程。

背景

下图描述了此解决方案背后的思路

用户向 Web 服务提交其请求。该请求被序列化为 XML,并与静态本地化 XML 合并。结果通过 XSLT 样式表,输出是 XSL-FO 文档。XSL-FO (https://w3schools.org.cn/xslfo/) 是一种用于格式化 XML 数据以输出到屏幕、纸张或其他媒体的语言。它可以被 FO.NET (http://fonet.codeplex.com/) 解析,以便生成 PDF 文档。

架构概述

以下 UML 图描述了解决方案中最重要类之间的关系。

每个类及其职责的简要说明

  • IPdfPrintableDocument:表示 PDF 可打印文档的基本接口
  • Document:表示一个非常简单的可打印文档
  • XmlTransformationManager:获取可打印文档实例并将其与相关的 XML 文化文件合并。输出是一个新的 XML 文件,该文件通过相关的 XSLT 文件。最终输出是可以被 FO.NET 解析的 XSL-FO 文档。
  • PdfPrinterDriver:借助 FonetDriver 类处理 PDF 生成过程。
  • ResourceManager:负责访问本地化和转换文件。它利用 CacheUtility 类将检索到的文件存储在缓存中,以加快后续请求的执行速度
  • CacheUtilitySystem.Web.Caching.Cache 类的简单包装器

设置

<pdfPrinterConfiguration>
    <settings
    xsltFolderPath="~/App_Data/Resources/XSLT/"
    localizationXmlFolderPath="~/App_Data/Resources/XML/"
    defaultCulture="en-US"
    defaultDateFormat="MM/dd/yyyy"
    pdfOutputFolder="~/GeneratedDocuments/"
      />
</pdfPrinterConfiguration>  

web.config 中,有一个小配置节

  • xsltFolderPath:告诉应用程序在哪里找到 XSLT 文件
  • localizationXmlFolderPath:告诉应用程序在哪里找到本地化文件
  • defaultCulture:默认应用程序文化
  • defaultDateFormatDateTime 格式
  • pdfOutputFolder:告诉应用程序在哪里保存 PDF 文件。

创建一个简单的 WCF 服务

我决定使用一个简单的 WCF 服务来测试 PDF 打印机核心。首先,让我们创建一个 IPrintableDocument

[Serializable, DataContract(Namespace = "http://Schemas/PdfPrinter/Common")]
public class Document : IPrintableDocument
{
    [DataMember(IsRequired = true)]
    public string Description;
 
    public string ToXml()
    {
        return ObjectXmlSerializer.SerializeObjectToXmlFormattedString(this);
    }
}  

然后,让我们编写一个带有请求和响应的简单 Web 服务

[ServiceBehavior(Namespace = "http://Schemas/PdfPrinter/WebServices")]
public class PdfPrinterService : IPdfPrinter
{
    public PdfPrinterResponse Print(PdfPrinterRequest request)
    {
        return PdfPrinterFacade.PrintPdf(request);
    }
} 
[MessageContract]
public class PdfPrinterResponse
{
    [MessageHeader]
    public string Info;
 
    [MessageBodyMember]
    public string Message;        
} 
[MessageContract]
public class PdfPrinterRequest
{
    private Document _document;
 
    [MessageBodyMember]
    public Document Document
    {
        get { return _document; }
        set { _document = value; }
    }        
}   

最后,让我们实现我们的 XSLT 和本地化 XML 文件,并将它们命名为 Document 类。

我将 Document.xslt 放在 "~/App_Data/Resources/XSLT/" 文件夹下。这是一个简单的 XSLT,展示了 XSL-FO 的一些强大功能。然后,我编写了一个简单的本地化 XML 文件 Document.xml 并将其放在 "~/App_Data/Resources/XML/" 文件夹下。

运行代码

让我们使用 Document.Description = "Hello" 调用我们的 Web 服务。

所有输入数据都合并在一个 XML 文档中,如下所示

<PdfPrinter>
  <Document>
    <Description>Hello</Description>
  </Document>
  <culture language="en-US">
 
    <label id="Footer" text="This documents has been auto-generated with Pdf Printer Service"/>
    <label id="Page" text="Page " />
    <label id="Of" text=" of " />
 
    <label id="Message" text="You sent the following message:" />
    <label id="EmptyMessage" text="- Empty message -" />
 
    <label id="Date_Header_1" text="Date" />
    <label id="LongWord_Header_2" text="Very Long Word" />
    <label id="Decimal_Header_3" text="Decimal" />
    <label id="Integer_Header_4" text="Integer" />
    <label id="Total" text="Total" />
 
    <label id="Date_Field_1" text="11/02/1984" />
    <label id="LongWord_Field_2" text="ThisIsAVeryVeryLongBreakedWord" />
    <label id="Decimal_Field_3" text="1234.5678" />
    <label id="Integer_Field_4" text="3" />
 
  </culture>
</PdfPrinter>

然后,XSLT 文件通过查询先前的 XML 内容来生成 XSL-FO 文档。它使用一个辅助扩展类 (PdfPrinter.Core.Common.XsltExtensionService),该类简化了一些常见操作。

. . .
<xsl:variable name="logo" 
    select="utilityExtension:MapPath('~/App_Data/Resources/IMAGES/logo.jpg')"/>
<fo:block>
   <fo:block>
   <fo:external-graphic src="url('{$logo}')" 
        content-width="auto" content-height="auto"/>
   </fo:block>
   <fo:block padding-top="2pt">
   <fo:block text-align="left" font-size="16pt">
   <fo:inline font-weight="bold">
   <xsl:value-of select="/PdfPrinter/culture/label[@id='Message']/@text"/>
   </fo:inline>
   </fo:block>
   <fo:block text-align="left" padding-top="2pt" font-size="16pt">
   <xsl:choose>
   <xsl:when test="/PdfPrinter/Document/Description != ''">
   <xsl:value-of select="/PdfPrinter/Document/Description"/>
   </xsl:when>
   <xsl:otherwise>
   <xsl:value-of select="/PdfPrinter/culture/label[@id='EmptyMessage']/@text"/>
   </xsl:otherwise>
   </xsl:choose>
   </fo:block>
   </fo:block>
</fo:block>
. . . 

最终结果由 FO.NET 解析,输出看起来非常棒!

结论

使用这种方法生成 PDF 文档的好处是,开发人员不必了解任何特定的 API,并且可以通过仅更改一些 XSLT 查询或添加新的 XSL-FO 语句来在运行时修改文档布局。

我希望我的工作对您有所帮助,并且使用起来愉快!

© . All rights reserved.