Shao Fan

          關于JAVA與軟件工程
          posts - 31, comments - 71, trackbacks - 0, articles - 4
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          轉換HTML內容為PDF格式(1)

          Posted on 2006-05-30 06:15 shaofan 閱讀(5689) 評論(7)  編輯  收藏 所屬分類: Java
          作者:Nick Afshartous

          英文原文:http://www.javaworld.com/javaworld/jw-04-2006/jw-0410-html.html

          翻譯:http://shaofan.blogjava.net

          把網頁內容以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來調用這三個工具時,這個問題就會得到解決。

          ?

          第一步:轉換HTMLXHTML

          ?

          第一步就是把HTML轉換為一個新的XHTML文件。當然,如果文件本來已經就是XHTML,那就不需要這一步了。

          ?

          我用JTidy來完成這個轉換。JTidyTidy 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(輸出)的內容如下:

          ?

          <html>

          <head>

          ? <title>Hello World

          </head>

          <body>

          ?? <p> Hello World!

          </body>

          </html>

          ?

          ?

          <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Strict//EN"

          ??? quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

          <html xmlns=quot;http://www.w3.org/1999/xhtml">

          <head>

          <meta name=quot;generator" content="HTML Tidy, see www.w3.org" />

          <title>Hello World</title>

          </head>

          <body>

          <p>Hello World!</p>

          </body>

          </html>

          ?

          要注意的是,在XML文件中的那個</p></title>JTidy自動添加的[譯注1]

          ?

          ?

          第二步:轉換XHTMLXSL-FO[譯注2]

          ?

          下面,XHTML將被轉換為XSL-FO,一種用來為XML文檔指定打印格式的語言。我通過用XSLT轉換器(Apache Xalan)處理XSL樣式表來完成這個轉換。我使用的樣式表是由Antenna House提供的xhtml2fo.xslAntenna 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-FOHTML語言。

          ?

          ??? <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 Xercesxml-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-FOPDF

          ?

          第三步,也就是最后一步,就是把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部分找到詳細說明。

          ---------------------------------------------------------------------------


          [譯注1] 此處原文是“在XML文件中的那個</p>JTidy自動添加的”。我使用JTidy轉換的結果是</title>也被添加,而且這符合JTidy的邏輯,因此這里稍作了修改。

          ?

          [譯注2] 這一部分我在試著做的時候遇到很多問題。首先,有些地方作者描述的并不清楚,特別是對于模板的解釋那一部分。其次,在用Xalan做轉換時遇到了Connection time out的異常。這可能是由于xml文件中的dtd(xhtml1-strict.dtd)無法連接造成的。把該dtd下載到本地后,該異常即可消除。然后是無法找ent文件。所需要的這些ent都可以在xmlbuddy的安裝包里找到,拷過來就可以了。我不知道作者是不是沒有遇到過這些問題,也可能我這只是特例。


          評論

          # re: 轉換HTML內容為PDF格式(1)  回復  更多評論   

          2006-05-30 17:27 by Fusion KISS
          這個轉換對html的標簽都支持么?

          # re: 轉換HTML內容為PDF格式(1)  回復  更多評論   

          2006-05-30 17:44 by shaofan
          @Fusion KISS

          這個你可以查一下那個xhtml2fo.xsl文件,看里面有沒有對你用到的那些標簽的定義。如果不是很偏的話,我想大部分都是有的。不過你也可以重新定義。

          # re: 轉換HTML內容為PDF格式(1)  回復  更多評論   

          2006-06-01 10:48 by for3w
          記得以前Dreamweaver4.0的時候就有一個插件,直接把HTML轉成PDF。

          # re: 轉換HTML內容為PDF格式(1)  回復  更多評論   

          2006-06-01 18:20 by shaofan
          @for3w

          這兩種方法的區別在于,這里介紹的這種,可以把HTML->PDF的功能集成到你的JAVA代碼里,可以實現隨時的,對任意網頁的動態轉換。之所以要動態,是因為網頁是動態的,每次展示的內容會不同。

          # re: 轉換HTML內容為PDF格式(1)  回復  更多評論   

          2006-08-17 17:32 by 寧采臣
          xhtml2fo.xsl的確是一個好東西。
          不過我下載了一個想測試一下功能,使用apache的coconn框架想把html轉化為pdf,為了保證兼容性,我先把html轉化為xhtml,再用xhtml2fo.xsl對xhtml進行轉化,可惜這里出錯了。。還在研究中。。
          感謝樓主

          # re: 轉換HTML內容為PDF格式(1)[未登錄]  回復  更多評論   

          2007-01-15 17:05 by Charles
          請問,你在轉換的過程中,pdf 的分頁是怎么控制的?
          謝謝~

          # re: 轉換HTML內容為PDF格式(1)  回復  更多評論   

          2008-07-22 03:26 by Hi
          how to keep the blank lines
          主站蜘蛛池模板: 柯坪县| 临泉县| 松潘县| 白玉县| 慈溪市| 泸州市| 武宁县| 连平县| 连城县| 云龙县| 汕头市| 开封县| 济宁市| 睢宁县| 灌南县| 思南县| 曲靖市| 佛山市| 育儿| 西林县| 伊宁市| 元谋县| 同江市| 名山县| 锡林浩特市| 扎囊县| 成安县| 武平县| 久治县| 阜康市| 永清县| 曲沃县| 蒙阴县| 漯河市| 阳东县| 奉新县| 霍州市| 西和县| 甘谷县| 商水县| 江阴市|