转换HTML内容为PDF格式

转载 2006年06月19日 22:00:00

作者:rainy14f

为网页提供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

使用命令行界面

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



使用这三个工具的命令行界面开始我们的工作是个好方法,尽管这种方法并不适合产品级的系统,因为它需要往磁盘中写入临时的中间文件。这种额外的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标签在翻译中被定义为:

[code]    <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的安装包里找到,拷过来就可以了。我不知道作者是不是没有遇到过这些问题,也可能我这只是特例。

把word文档内容转换为pdf格式经验分享

从事办公文书的朋友们是否有过这样的感触:为了编辑的方便有时需要自己制作好的Word方案转为PDF格式,然后再分享给他人阅读,那么如何将Word完整地转换为PDF呢?这里笔者将自己总结的经验分享给大家,...
  • isongve
  • isongve
  • 2015年07月08日 10:12
  • 237

pdf转换成html网页格式的方法

日常工作中需要用到好多文件格式,如PDF、Word、Excel、TXT和HTML等等。什么是HTML呢?HTML也成为网页文件,即可以通过各种浏览器来打开。互联网越向前发展,HTML文件也越来越常用,...

PHP 转换PDF、TXT、HTML以及图像等格式的方法

这几天一直在使用PHP开发一个不同文件类型转换的项目,清源这里将各种文件格式转换的方法分享给大家,有需要的朋友可以参考一下,谢谢! 1、将PDF转换成JPG - PDF2JPG 这...

HTML转换为PDF格式

package com.util; import java.io.FileNotFoundException; import java.io.FileOutputStream; import j...

将html文件转换为pdf格式的文件

将html文件转换为pdf文件,代码如下: OutputStream os = null; try { File f = new File("F:/Test")...
  • uk8692
  • uk8692
  • 2013年11月29日 19:30
  • 931

pdf转换成html格式的步骤

或许你会疑惑,为何选择将PDF、Word、Excel、及PPT这些格式文件转化成HTML呢?大家知道,PDF作为一种跨平台的电子文档格式,有时需要将文件放置到网络上进行分享时,HTML则是最佳格式。H...
  • mikinh
  • mikinh
  • 2015年08月19日 11:58
  • 219

pdf怎么转换成html格式的文件

把PDF格式转换成HTML网页内容,有利于内容的传播。在一些应用中,提供格式便于打印文档时一个必须的功能,比如等。对于网页设计者而言,将一些有用的资料汇编在一个网页中,可以提高页面的展示机会,而往往很...

转换 HTML 与 PDF 格式文档的神器

企业 Web 项目开发中经常会有生产 PDF 格式文档的需求,例如 PDF 账单下载,月末生成各种统计报表等等。我们要帮助企业实现自动化,也就是说无需人工干预,程序能够按需从 DB 中拿数据自主生成。...
  • wide288
  • wide288
  • 2014年12月17日 15:24
  • 3510

pdf格式的电子如何转换成html

将PDF转换成HTML其实并没有你想象中那么难,你当然可以选择手动复制粘贴将PDF文件内容直接剪切成为网页格式,但是这种方法十分耗时耗力,并不能提高我们的工作效率。   事实上,要实现PDF转换...
  • mikinh
  • mikinh
  • 2015年08月21日 11:57
  • 324

利用TCPDF将html页面转换为PDF格式

使用TCPDF首先从TCPDF官网获取最新版本:http://www.tcpdf.org。官网提供了几十个示例以及说明文档,下载解压后一定要注意文件路径,如何使用TCPDF,可以从以下5个步骤完成: ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:转换HTML内容为PDF格式
举报原因:
原因补充:

(最多只允许输入30个字)