`
jiaoyingjun
  • 浏览: 20844 次
  • 性别: Icon_minigender_1
  • 来自: 安徽
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

为网页提供PDF文件支持

阅读更多
为网页提供PDF文件支持

概要

在这篇文章里,Nick Afshartous描述了一种把HTML的内容转换为PDF格式的方法。这种方法相当有用,比如说,一个web程序可以在它的页面上提供如“下载为PDF”的功能。这种功能方便了打印和储存,以供日后使用。Afshartous的转换方法只使用开源的组件。也有一些商业产品可供使用。因此,在这篇文章里描述的这种方法既在价格上可以承担,又能够获得所用组件的源码。

把网页内容以PDF的格式呈献有利于内容的传播。在一些应用中,提供格式便于打印的文档是一个必需的功能,比如员工利益表等。事实上,法律规定Summmary Plan Descriptions(SPDs)必须能够打印,即使它们是在线提供的也是如此。然而只打印网页本身是不够的,因为打印格式必包含表格内容和页码。

为了提供这样的功能,开发人员可以把HTML内容转换为PDF格式。在此即做介绍。这里介绍的这种方法只使用开源组件。一些商业产品也支持动态的文档生成,比如说Adobe,它有Document Server产品线。但是,使用商业产品的开销是相当可观的。使用开源方案可以缓解开销的问题,并增加了组件源码的透明度。

转换过程包含以下三步:
1.把HTML转换为XHTML;
2.把XHTML转换为XSL-FO(Extensible Stylesheet Language Formatting Objects扩展样式表语言格式化对象)。这里使用XSL样式表和XSLT转换器;
3.把XSL-FO文档传递给格式化程序来生成目标PDF文档。

本文先介绍怎样用命令行界面来做这种转换,然后介绍怎样在JAVA中使用DOM接口来做同样的工作。

组件版本:
本文中的代码在以下版本中进行了测试:
组件     版本
JDK     1.5_06
JTidy    r7-dev
Xalan-J  2.7
FOP     0.20.5

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Nick Afshartous;rainy14f(作者的blog:http://blog.matrix.org.cn/page/rainy14f)
原文:http://www.javaworld.com/javaworld/jw-04-2006/jw-0410-html.html
Matrix:http://www.matrix.org.cn/resource/article/44/44489_HTML+PDF.html
关键字:HTML;PDF


使用命令行界面

在转换过程中的每一步都包含了从一个输入文件生成输出文件的过程。这个过程可以用下图来表示:



使用这三个工具的命令行界面开始我们的工作是个好方法,尽管这种方法并不适合产品级的系统,因为它需要往磁盘中写入临时的中间文件。这种额外的I/O会导致性能的降低。稍后,在我们用JAVA来调用这三个工具时,这个问题就会得到解决。

第一步:转换HTML为XHTML

第一步就是把HTML转换为一个新的XHTML文件。当然,如果文件本来已经就是XHTML,那就不需要这一步了。

我用JTidy来完成这个转换。JTidy是Tidy HTML解析器的JAVA版本。在转换的过程中,JTidy会自动添加缺少的标签来创建格式良好(well-formed)的XML文档。我用的是在SourceForge上的最新版本r7-dev。

可以用以下的脚本来运行JTidy:
#/bin/sh
java -classpath lib/Tidy.jar org.w3c.tidy.Tidy -asxml $1 >$2

此脚本设置了CLASSPATH并调用了JTidy。运行时,要输入的文件是以命令行参数的形式传给JTidy。默认情况下,生成的XHTML将被输出到标准输出设备。-modify开关可以用来覆写输入文件。-asxml开关把JTidy的输出重定向到格式良好的XML。

调用时像这样:
tidy.sh hello.html hello.xml

hello.html(输入)和hello.xml(输出)的内容如下:

<p>Hello World!</p>是JTidy自动添加的[译注1]。第二步:转换XHTML为XSL-FO[译注2]下面,XHTML将被转换为XSL-FO,一种用来为XML文档指定打印格式的语言。我通过用XSLT转换器(Apache Xalan)处理XSL样式表来完成这个转换。我使用的样式表是由Antenna House提供的xhtml2fo.xsl。Antenna House是一个出售XSL-FO上商用格式程序的公司。xhtml2fo.xsl样式表指定了如何把每个HTML标签翻译成相应的XSL-FO格式化命令序列。举例来说,HTML中的H2标签在翻译中被定义为:    <xsl:template match="html:h2">      <fo:block xsl:use-attribute-sets="h2">        <xsl:call-template name="process-common-attributes-and-children"/>      </fo:block>    </xsl:template>

在处理的过程中,每次遇到H2标签,以上XSLT模板都会被调用。html:前缀表明H2标签是HTML的命名空间(namespace)。样式表的命名空间在顶层xsl:stylesheet指示符的属性中被指定。在xhtml2fo.xsl的最顶层,我们可以看到它指定了三个命名空间,分别对应于XSL,XSL-FO和HTML语言。

    <xsl:stylesheet version="1.0"                    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"                    xmlns:fo="http://www.w3.org/1999/XSL/Format"                    xmlns:html="http://www.w3.org/1999/xhtml">...

模板中的第二行

    <fo:block xsl:use-attribute-sets="h2">

致使fo:block标签被输出,并且H2的属性被生成为fo:block标签的属性和值。每个XSL-FO块(block)都是一段文字,它们的格式基于块的属性的值。

H2的属性在样式表中被定义为:

    <xsl:attribute-set name="h2">        <xsl:attribute name="start-indent">10mm        <xsl:attribute name="end-indent">10mm        <xsl:attribute name="space-before">1em        <xsl:attribute name="space-after">0.5em        <xsl:attribute name="font-size">x-large        <xsl:attribute name="font-weight">bold        <xsl:attribute name="color">black   </xsl:attribute-set>

start-indent及其后的属性用来指定H2块的格式化后的外观。当你想改变PDF文档中用同样HTML标签的文字块的外观时,使用属性集可以使这种改变更加容易。只要改动属性的设置,那么输出的文件中所有使用这些属性的地方都会被改动。

下一个指示符调用一个名为"process-common-attributes-and-children"的模板:

    <xsl:call-template name="process-common-attributes-and-children"/>

这个模板在样式表中被指定。它的作用是检查一些普通的HTML属性(如lang,id,align,valign,style)并生成相应的XSL-FO指示符。要触发对嵌在顶层H2标签中的任意标签的翻译,process-common-attributes-and-children会调用:

    <xsl:apply-templates/>

因此,如果输入是

    <h2> Hello <em> there </em> </h2>

那么在H2的模板中的<xsl:apply-templates/>就会触发用来翻译<em>标签的模板。

翻译H2标签的输出是:

    <fo:block start-indent="10mm" ...
        original H2 tag content
      </fo:block>

我们调用Xalan来应用xhtml2fo.xsl。在调用Xalan之前,用Unix脚本xalan.sh来设置它需要用到的CLASSPATH变量。

#/bin/sh

export CLASSPATH='.;./lib/xalan.jar;./lib/xercesImpl.jar;./lib/xml-apis.jar;lib/serializer.jar'

java -classpath $CLASSPATH org.apache.xalan.xslt.Process -IN $1 -XSL xhtml2fo.xsl -OUT $2 -tt

因为Xalan需要一个XML解析器,所以这里还需要Apache Xerces和xml-api JARs。所有的jar文件都可以在Xalan的发布包中找到。

要通过对XHTML应用样式表来新建一个XSL-FO文件,可以调用脚本:

    xalan.sh  hello.xml hello.fo

我喜欢用Xalan的跟踪开关(-tt)来显示应用的模板。hello.fo文件如下:

<?xml version="1.0" encoding="UTF-8"?><fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"    xmlns:html="http://www.w3.org/1999/xhtml"    writing-mode="lr-tb"    hyphenate="false"    text-align="start"    role="html:html">  <fo:layout-master-set>    <fo:simple-page-master page-width="auto" page-height="auto"                           master-name="all-pages">      <fo:region-body column-gap="12pt" column-count="1" margin-left="1in"                      margin-bottom="1in" margin-right="1in" margin-top="1in"/>      <fo:region-before display-align="before" extent="1in"                        region-name="page-header"/>      <fo:region-after display-align="after" extent="1in"                      region-name="page-footer"/>      <fo:region-start extent="1in"/>      <fo:region-end extent="1in"/>    </fo:simple-page-master>  </fo:layout-master-set>  <fo:page-sequence master-reference="all-pages">    <fo:title>Hello World    <fo:static-content flow-name="page-header">      <fo:block font-size="small" text-align="center" space-before="0.5in"                space-before.conditionality=;"retain">        Hello World      </fo:block>    </fo:static-content>    <fo:static-content flow-name="page-footer">      <fo:block font-size="small" text-align="center" space-after="0.5in"                space-after.conditionality=quot;retain">        - <fo:page-number/> -      </fo:block>    </fo:static-content>    <fo:flow flow-name="xsl-region-body">      <fo:block role="html:body">        <fo:block space-before="1em" space-after="1em" role="html:p">          Hello World!        </fo:block>      </fo:block>    </fo:flow>  </fo:page-sequence></fo:root>


第三步:XSL-FO到PDF

第三步,也就是最后一步,就是把XSL-FO文档传递给格式化程序来生成PDF。我用的是Apache FOP(Formatting Objects Processor)。FOP部分实现了XSL-FO标准,并对PDF的输出格式提供了最好的支持。而对Postscript还处于初级阶段,对微软的RTF的支持还在计划中。FOP发布版包含shell脚本fop.sh/fop.bat,它们需要传入XSL-FO文件作为输入参数来生成目标PDF文件。

在Unix下可以这样运行:

    fop.sh hello.fo hello.pdf

唯一所需的前提条件就是把设置为这个脚本使用到的FOP目录设置环境变量。

文件hello.pdf即为FOP的输出,你在本文的源代码中可以找到。

因为FOP目前并未完全实现XSL-FO标准,所以有一定的局限性。具体它实现了标准的哪些子集,可以在FOP的网站上的Compliance部分找到详细说明。

Java 程序

通过使用上述步骤中用过的三个工具的DOM API,我接下来会展示一个JAVA程序。它在运行时需要提供两个命令行参数,会自动生成相应的PDF文档,并且不会产生任何临时文件。

第一个程序新建一个HTML文件的InputStream对象,然后此对象被传给JTidy。

JTidy有个方法叫parseDOM(),可以用来生成输出的XHTML文档的Document对象。

    public static void main(String[] args) { 
    // 打开文件   
      if (args.length != 2) {  
     System.out.println("Usage: Html2Pdf htmlFile styleSheet");     
     System.exit(1);  
     } 
     FileInputStream input = null;  
     String htmlFileName = args[0]; 
     try
       {
        input = new FileInputStream(htmlFileName); 
       } 
     catch (java.io.FileNotFoundException e)
     {       
     System.out.println("File not found: " + htmlFileName);   
     }      
     Tidy tidy = new Tidy(); 
     Document xmlDoc = tidy.parseDOM(input, null);

JTidy的DOM实现并不支持XML命名空间。因此,我们必需修改Antenna House的样式表,让它使用默认的命名空间。比如,原来是:

    <xsl:template match="html:h2">      <fo:block xsl:use-attribute-sets="h2">        <xsl:call-template name="process-common-attributes-and-children"/>      </fo:block>    </xsl:template>

被修改后是:

    <xsl:template match="h2">      <fo:block xsl:use-attribute-sets="h2">        <xsl:call-template name="process-common-attributes-and-children"/>      </fo:block>    </xsl:template>

这个改动必需被应用到xhtml2f0.xsl中的所有模板,因为JTidy生成的Document对象以标签作为根,如:



修改后的xhtml2fo.xsl包含在这篇文章附带的源代码中。

接着,xml2FO()方法调用Xalan,使样式表应用于JTidy生成的DOM对象:

    Document foDoc = xml2FO(xmlDoc, args[1]); 

方法xml2FO()首先调用getTransformer()来获得一个指定的样式表的Transformer对象。然后,代表着转换结果的那个Document被返回:

    private static Document xml2FO(Document xml, String styleSheet) {    DOMSource xmlDomSource = new DOMSource(xml);          DOMResult domResult = new DOMResult();    Transformer transformer = getTransformer(styleSheet);    if (transformer == null) {        System.out.println("Error creating transformer for " + styleSheet);        System.exit(1);    }    try {        transformer.transform(xmlDomSource, domResult);    }    catch (javax.xml.transform.TransformerException e) {        return null;    }    return (Document) domResult.getNode();    }

接着,main方法用与HTML输入文件相同的前缀来打开一个FileOutputStream。然后调用fo2PDF()方法所获得的结果被写入OutputStream:

    String pdfFileName = htmlFileName.substring(0, htmlFileName.indexOf(".")) + ".pdf";    try {        OutputStream pdf = new FileOutputStream(new File(pdfFileName));        pdf.write(fo2PDF(foDoc));    }    catch (java.io.FileNotFoundException e) {        System.out.println("Error creating PDF: " + pdfFileName);    }    catch (java.io.IOException e) {        System.out.println("Error writing PDF: " + pdfFileName);    }

方法fo2PDF()会使用在转换中产生的XSL-FO Document和一个ByteArrayOutputStream来生成一个FOP driver对象。通过调用Driver.run可以生成PDF文件。结果被作为一个byte array返回:

    private static byte[] fo2PDF(Document foDocument) {        DocumentInputSource fopInputSource = new DocumentInputSource(                                                         foDocument);        try {            ByteArrayOutputStream out = new ByteArrayOutputStream();            Logger log = new ConsoleLogger(ConsoleLogger.LEVEL_WARN);            Driver driver = new Driver(fopInputSource, out);            driver.setLogger(log);            driver.setRenderer(Driver.RENDER_PDF);            driver.run();            return out.toByteArray();        } catch (Exception ex) {            return null;        }    }

Html2Pdf.java的源代码可以在这篇文章的附带代码中找到。

使用DOM API来完成这整个过程,速度要比使用命令行界面快得多,因为它不需要往磁盘中写入任何中间文件。这种方法可以集成到服务器里,来处理并发的HTML-PDF转换请求。

以前我曾以这里展示的这个程序为基础把生成PDF的功能集成到一个WEB应用。而生成PDF的过程是动态的,因此不需要考虑WEB页面和相应PDF同步的问题,因为生成的PDF文件并不是存放在服务器上。

结论

综述,在本文里我描述了怎样利用开源组件来实现HTML到PDF的转换。虽然这种实现方法在价格和源码方面很有吸引力,但同时也有一定的折衷。一些商业组件可以提供更完整的标准实现。

比如说,FOP目前的版本是.91,不完全支持XSL-FO标准。尽管如此,相对其它的格式而言,对PDF提供了更多的支持。

在开始一个文档转换的项目之前,你必需考虑对文档格式的需求,并把它们与已有组件所实现的功能做个对比。这将有助于做出正确的决定。

资源

# 下载本文中的源码:http://www.javaworld.com/javaworld/jw-04-2006/html/jw-0410-html.zip
# Adobe's Document Server 产品:http://www.adobe.com/products/server/documentserver/main.html
# Antenna House (出售商业的格式化程序):http://www.antennahouse.com
# xhtml2fo.xsl 把 XHTML 转化为 XSL-FO 的样式表:http://www.antennahouse.com/XSLsample/XSLsample.htm
# Apache FOP formatter 把 XSL-FO 翻译为 PDF:http://xmlgraphics.apache.org/fop
# FOP 对 XSL-FO 标准的兼容性:http://xmlgraphics.apache.org/fop/compliance.html
# JTidy,把 HTML 转化为 XHTML:http://sourceforge.net/projects/jtidy/
# Xalan:http://xalan.apache.org/
# Matrix:http://www.matrix.org.cn

附注

[译注1]此处原文是“在XML文件中的那个</p>是JTidy自动添加的”。我使用JTidy转换的结果是也被添加,而且这符合JTidy的逻辑,因此这里稍作了修改。

[译注2]这一部分我在试着做的时候遇到很多问题。首先,有些地方作者描述的并不清楚,特别是对于模板的解释那一部分。其次,在用Xalan做转换时遇到了Connection time out的异常。这可能是由于xml文件中的dtd(xhtml1-strict.dtd)无法连接造成的。把该dtd下载到本地后,该异常即可消除。然后是无法找ent文件。所需要的这些ent都可以在xmlbuddy的安装包里找到,拷过来就可以了。我不知道作者是不是没有遇到过这些问题,也可能我这只是特例。
http://www.matrix.org.cn/resource/article/2006-06-19/HTML+PDF_44489.html
分享到:
评论
2 楼 zoutuo 2010-04-09  
转载也不负点责任,少了那么多东西还帖出来
1 楼 elvishehai 2009-09-16  
都没有附件的....无聊!!!

相关推荐

    网页在线读取pdf文件代码示例

    综上所述,网页在线读取PDF文件涉及的技术面较广,从基础的PDF文件格式理解,到浏览器特性,再到JavaScript库的应用,每个环节都需要细致考虑,以提供稳定、高效且用户体验良好的在线阅读环境。

    C#网页转pdf文件

    在本场景中,我们关注的是如何利用C#将HTML网页内容转换为PDF文件,这一技术对于数据报告、电子书籍制作或者网页保存等方面非常实用。下面将详细介绍这个过程及其相关知识点。 首先,我们需要理解HTML和PDF之间的...

    web网页保存为PDF文件

    在IT行业中,将Web网页保存为PDF文件是一种常见的需求,特别是在文档分享、打印预览或者离线阅读等场景。PDF(Portable Document Format)格式能够很好地保留网页的布局和样式,使得用户可以在不同的设备上查看而...

    易语言PDF文件制作库,支持PNG和静态编译带例子源码

    在日常开发过程中,对PDF文档的处理是一个常见的需求,而易语言PDF文件制作库的出现,为易语言开发者提供了一个强大的解决方案,实现了在易语言环境中创建和处理PDF文档的便捷性。 易语言PDF文件制作库的一大特色是...

    网页打开pdf文件

    网页上打开PDF文件是互联网应用中常见的需求,尤其在学术交流、在线阅读和文档分享等领域。PDF(Portable Document Format)格式因其跨平台性和文件保真性而被广泛使用。本篇将详细介绍如何在网页上实现PDF文件的...

    html5手机网页PDF文件阅读器插件

    总的来说,HTML5手机网页PDF文件阅读器插件是通过利用HTML5、JavaScript和相关技术,为移动设备上的Web应用程序提供的一种便捷的PDF查看方式。它不仅可以提升用户体验,还可以结合离线存储技术提高可用性。通过学习...

    java URL转PDF文件(完美支持中文)

    在Java编程环境中,将URL内容转换为PDF文件是一项常见的需求,尤其在数据抓取、文档保存或自动化报告生成等场景中。"java URL转PDF文件(完美支持中文)"的主题着重于如何利用Java库来实现这一功能,并且确保中文字符...

    谷歌浏览器网页保存为PDF

    - **良好的跨平台兼容性**:PDF文件可以在几乎所有的操作系统上无差异显示; - **易于阅读**:PDF格式保持了原始网页的布局和样式,方便用户阅读和打印; - **完整性**:不仅能够保存网页中的文本,还能保留图片和...

    一键保存网页为PDF_V1.2

    "一键保存网页为PDF_V1.2"充分利用了这一特性,确保转换后的PDF文件能够准确地保留网页原有的图像、文字和排版,使得读者在离线状态下也能获得与在线阅读相似的体验。 在实际操作中,用户只需简单几步就能完成转换...

    java URL转PDF文件

    在Java编程环境中,将一个URL链接的内容转换为PDF文件是一个常见的需求,这通常涉及到网络请求、HTML解析以及PDF生成等多个步骤。以下是一个详细的步骤指南,涵盖了这个过程中的关键知识点: 首先,我们需要处理URL...

    PHP实现在线阅读PDF文件的方法

    PHP实现在线阅读PDF文件的方法是一种在Web服务器端使用PHP编程语言来实现在网页上直接查看PDF文档的技术...通过这种方式,开发者可以为用户提供一个便捷的在线阅读PDF文件的平台,满足用户在网页中直接查看文档的需求。

    基于PDF.js的pdf文件预览.rar

    "基于PDF.js的pdf文件预览.rar"的资源提供了一个方便的示例,演示如何在网页中实现PDF文件的预览、下载和打印功能。 首先,PDF.js的核心在于它的渲染引擎,它能够解析PDF文件的字节流,并将其转化为可交互的...

    网页转换为 PDF 文件(插件).zip

    网页转换为PDF文件是日常工作中常见的一项需求,尤其在处理电子文档、保存网页信息或进行打印预览时。本文将详细介绍如何使用插件实现这一功能,并解析提供的压缩包文件内容。 首先,我们要知道,PDF(Portable ...

    PDF文件制作全攻略

    PDF文件制作全攻略 在数字化文档的领域里,PDF(Portable Document Format)文件因其跨平台、格式稳定的特点,被广泛应用于各种场景。本教程将深入探讨如何高效地创建和编辑PDF文件,帮助用户掌握PDF文件制作的全...

    PHP网页生成PDF文件

    在PHP中生成PDF文件是一项常见的任务,特别是在需要将网页内容导出为可打印或可下载的文档时。本文将深入探讨如何使用PHP实现这一功能,包括介绍常用的库、方法和最佳实践。 首先,PHP生成PDF文件主要依赖于第三方...

    PDF文件拆分工具

    例如,你可以按页面拆分,将PDF文件中的每一页单独保存为一个文件;也可以按书签或章节拆分,如果PDF文件有嵌入的书签或结构,工具会根据这些信息进行拆分;还可以设置自定义范围,仅拆分指定的页面。 4. **在线与...

    把PDF文件展示在前端页面

    总的来说,`touchpdf-master`压缩包提供了一个前端展示PDF的解决方案,通过解析PDF文件并在前端渲染,结合CSS定制样式和JavaScript实现交互,可以为用户提供无缝的在线阅读体验。在实际项目中,你需要根据具体需求...

    web 在线浏览PDF文件

    本文将深入探讨如何利用pdf.js实现在网页中安全地在线浏览PDF文件,以及相关的技术要点。 pdf.js是Mozilla开发的一个开源项目,其目标是提供一个跨平台、基于Web的标准来展示PDF文件,无需依赖任何特定的浏览器插件...

    将网页保存成pdf文件。

    在IT行业中,转换网页为PDF文件是一项常见的需求,特别是在文档归档、无格式化阅读或打印预览等方面。本篇文章将详细讲解如何使用Python编程语言,结合ChromeDriver工具,实现网页到PDF的转换,并着重讨论如何自定义...

Global site tag (gtag.js) - Google Analytics