術語
XSL-FO(XML?Stylesheet?Language-Formatting?Objects)
XSL-T(XSL?Transformations)
前言?本文介紹了FOP及其相關的技術,比如XSL-FO,FO工具等,以及如何利用這些工具進行文檔轉換(最后將會介紹利用WH2FO把Word文檔轉換成PDF)。
FOP介紹FOP?(Formatting?Objects?Processor)?是第一個基于XSL:FO的打印格式處理器,也是第一個與輸出無關的格式處理器。它是一個Java程序,能夠從對象樹中讀入然后生成渲染過的頁面輸出到指定的流。目前支持的輸出格式有PDF,PCL,PS,SVG,XML(以樹形結構表示),打印機,AWT,MIF和TXT。最主要的輸出指的是PDF。
James?Tauber?-?FOP?的最初作者。他開發了該工具的原始版本,而且很大方地開放了該代碼,后來又將它移交給?Apache?XML?Project。(他還給該工具選了一個極好的名稱;除了該名稱是字首組合詞之外,Webster?對?fop?的定義是“過分講究外表的人”。)現在?James?是?Bowstreet?的首席?XML?設計師。Apache?XML項目以開放與合作的方式開發的,提供商業品質的基于XML標準的解決方案。從標準的實施角度看,它能給標準機構(比如IETF和W3C)提供反饋信息。
XSL:FO介紹XSL?Formatting?Objects?(XSL-FO)?是Extensible?Stylesheet?Language?(XSL)的第二部分.?XSL-FO具體的說是一個XML的應用?,它描述了當頁面展現給讀者的時候它應該是什么樣的。一個樣式表(style?sheet)使用XSL-T語言(XSL?transformation?language)?把一個以語義詞匯組織的XML文檔轉換到另一個以表象的XSL-FO詞匯。?雖然有人會期望Web瀏覽器將來能夠知道如何直接把以使用XSL-FO標記的文檔顯示出來,但是現在有一個額外的轉換步驟是必需的,把生成的fo文檔進一步轉換成其他的格式,比如Adobe的PDF。
FO工具JFOR(Java?xsl-FO?to?Rtf?converter)是把依照XSL-FO規范的XML文檔轉換成RTF(Rich-Text?Format)格式,?它的目的與把XSL-FO(通常用XSLT生成)文檔轉換成PDF(使用FOP或其他類似的工具)相似。
WH2FO及其驗證工具FOA的作者是Fabio?Giannetti。WH2FO?是一個處理由Word?2000產生的HTML的Java應用程序,并把它們轉換成XML內容文件和XSL樣式表文件。從這些文件,一個標準的XSLT處理器可能獲得只含有XSL-FO標記的fo文件。也可以應用樣式表把XML文件轉換成HTML,這樣做就丟棄了Word額外補充進來的標記。使用XSL-FO?Render,比如FOP,能夠進一步渲染成PDF。
看它是如何工作的?

?
XSL?格式對象及它的特性XSL-FO?提供了一個比HTML+CSS更為復雜的視覺布局樣式(visual?layout?model)。一些格式,包括right-to-left?和?top-to-bottom?text,?footnotes,?margin?notes,?page?numbers?in?cross-references等等是XSL-FO支持的,而HTML+CSS卻不能。特別指出,雖然CSS?(Cascading?Style?Sheets)主要的應用在Web上,XSL-FO卻被設計成更為廣泛的用途。比如你能夠寫一個使用fo的XSL?style?布置整個一本能打印的書。而另外一個不同的樣式表(style?sheet)能夠轉換同一個XML文檔使它用在Web上。
[i]格式對象[/i]
確切的說,一共有56種XSL格式對象元素,它們都定義在http://www.w3.org/1999/XSL/Format?名字空間里。在這56個元素中,大部分表示各種矩形的區域(rectangular?area)。在余下的大部分中的元素是area和space的container。
XSL格式模型(formatting?model)是基于叫做區域(area)的矩形的盒子(rectangular?boxes),它能包括text,?empty?space,?images,?or?other?格式對象(formatting?objects).?就像CSS的box一樣,area在每個方向都有borders和padding,但CSS的margins被XSL的space-before和space-after替換了。XSL格式程序(XSL?formatter)讀格式對象(formatting?objects)來決定哪些area被放在頁面的哪些位置。許多格式對象產生一個areas(在大多數情況下),但是因為存在page?breaks,?word?wrapping,?連字符(hyphenation)和其他一些需要考慮到的細節當我們把潛在有大量的文本填充到有限的空間的時候,一些格式對象偶爾會產生多于一個的?area。
格式對象相互之間最主要的區別在于它們所表示的不同。例如:fo:list-item-label?格式對象是一個盒子(box),它包含著一個子彈符號(bullet),一個數字,或者是被放置在列表項(list?item)前面其它的指示符。fo:list-item-body格式對象也是一個盒子(box),它包含文本(text),列表項(list?item)沒有標簽(label)。fo:list-item?格式對象同樣是一個盒子,它同時包含了標簽(fo:list-item-label)和列表(fo:list-item-body)對象。
在處理的時候,fo文檔被分成數個頁面。Web瀏覽器窗口通常通常被當成一個很長的頁面來處理。可打印的格式通常包含多個單獨的頁面,每個頁面包含許多areas。主要有四種主要的?areas:
1.?regions
2.?block?areas
3.?line?areas
4.?inline?areas
這些形成了一個大概的層次關系(rough?hierarchy)。Regions?可以包含?block?areas。?Block?areas?可以包含其它的?block?areas,?line?areas,和內容(content)。Line?areas?可以包含?inline?areas。Inline?areas?可以包含其它的?inline?areas?和內容(content)。
Region是XSL-FO中定義的最高級別(highest-level?)的容器(container)。你可以想象這篇文章的一頁包含三個regions:?the?header,?the?main?body?of?the?page,?and?the?頁腳(footer).?格式對象產生的regions包括fo:region-body,?fo:region-before,?fo:region-after,?fo:region-start,?和fo:region-end.
Block?area?表示block級(block-level)的元素,例如?a?paragraph?or?a?list?item.?雖然block?areas?可以包含其他的?block?areas,?但通常在開始前和每一個block?area之后有一個換行符(line?break)?。block?area?被順序的放到包含它的容器中,要優于使用使用坐標精確定位的方式。當其它的?block?areas?被加入到它的前面或者是里面的時候,它會隨著變換坐標以產生需要的空間。Block?area?可以包含被解析過的字符數據,inline?areas,?line?areas和其它的block?areas,它們都被順序的安排在容器block?area中。能夠產生block?areas的格式對象包括?fo:block,?fo:table-and-caption,?和fo:list-block.
Line?area?表示在block內的一行文本。例如在這個清單中的每一行就是一個line?area。Line?areas可以包含inline?areas?和inline?spaces。沒有相應的格式對象對應于line?areas。替代的,當格式引擎(formatting?engine)決定如何把行包裹在block?areas中的時候,它會計算出line?areas。
Inline?areas?是行的一部分,例如單獨一個字符,一個腳注引用(footnote?reference)或者是一個數學方程式(mathematical?equation)。Inline?areas?可以包含其他的?inline?areas?和純文(raw?text)。能夠產生inline?areas的格式對象包括fo:character,?fo:external-graphic,?fo:inline,?fo:instream-foreign-object,?fo:leader,?和fo:page-number.
[i]格式特性[/i]
從整體考慮,XSL-FO文檔中的各種格式對象指定內容被放置到頁面中的次序。然而,格式特性(formatting?properties)格式的細節,比如size,?position,?font,?color,?and?a?lot?more.?格式特性被當作屬性(attributes)作用于單個的格式對象元素。
這些特性的許多細節類似于CSS。下面將說明的是CSS和XSL-FO使用相同的名字表示同一種東西。例如CSS?font-family?特性(property?)與XSL?font-family特性(property)表示的是同一種東西,雖然賦值的語法各不相同,但是這些值本身的含義卻都是一樣的。?為了表示?fo:block?元素(element)是使用某種近似于Times格式,你可能會用到下面的CSS規則:
fo:block?{font-family:?'New?York',?'Times?New?Roman',?serif}
使用XSL-FO?在fo:block中包含font-family?屬性的的等價規則是:
<fo:block?font-family="'New?York',?'Times?New?Roman',?serif">
可以很淺薄的認為這就是它們的不同,但是它們的樣式(style)名稱(font-family)和樣式(style)值('New?York',?'Times?New?Roman',?serif?)都是一樣的。CSS的font-family特性是一列用逗號隔開的字體名稱表,按照選項從頭到尾依次排列。XSL-FO的?font-family特性也是一列用逗號隔開的字體名稱表,按照選項從頭到尾依次排列。CSS和XSL-FO引用的字體名稱都包含空格,而且它們都會把關鍵字serif看做是獨一無二的serif字體。
當然,XSL格式對象支持的很多特性是CSS所沒有的。比如destination-placement-offset,?block-progression-dimension,?character,?和hyphenation-keep。必須學習它們才能獲得使用XSL的所有優勢。?
轉換到格式對象XSL-FO?是一個完全的XML詞匯表,它是為了把文本布置到頁面。XSL-FO文檔是用它的詞匯組織完好的XML文檔。這就意味著,它有XML的定義,根(root)元素,子(child)元素和其它等等。它必須追隨其它任何完好組織的XML文檔,否則格式程序(formatters)將不會接受它。按照約定(convention),含有XSL格式對象的文件可以使用三個字母的.fob文件后綴,或者是兩個字母的.fo后綴。然而,它也可以使用.xml后綴,因為它同樣也是完好組織的XML文件。
列表1是一個使用XSL格式對象標記的簡單文檔。這個文檔的根是fo:root,這個元素包含一個fo:layout-master-set和一個fo:page-sequence元素。fo:layout-master-set元素包含fo:simple-page-master,它是描述內容將被放置于此的一種頁面子元素。這個文檔僅僅是一個非常簡單的頁面,但是更復雜的文檔將有不同的master?page,它會為first,?right,?和?left,?body?pages,?front?matter,?back?matter,?等等其它產生定義,?每一個也會潛在有同的空白邊(margins),頁數(page?numbering)和其他特征。Page?master被引用的名稱在master-name屬性里面定義。
內容被放置在mater?page的拷貝fo:page-sequence里面。fo:page-sequence有一個master-reference的屬性標識被引用的master?page的名稱。它的fo:flow子元素包含真正的被放置到頁面的內容。這里的內容是由兩個fo:block的子元素表示的,每一個定義了20個像素點,font-family為serif和行高(line-height)為30個像素點的屬性。
列表1:一個簡單的XSL-FO文檔
<?xml?version="1.0"?>
<fo:root?xmlns:fo="http://www.w3.org/1999/XSL/Format">
??<fo:layout-master-set>
????<fo:simple-page-master?master-name="only">
??????<fo:region-body/>
????</fo:simple-page-master>
??</fo:layout-master-set>
??<fo:page-sequence?master-reference="only">
????<fo:flow?flow-name="xsl-region-body">
??????<fo:block?font-size="20pt"?font-family="serif"
????????????????line-height="30pt">
????????Hydrogen
??????</fo:block>
??????<fo:block?font-size="20pt"?font-family="serif"
????????????????line-height="30pt"?>
????????Helium
??????</fo:block>
????</fo:flow>
??</fo:page-sequence>
</fo:root>
雖然可以手工書寫像列表1那樣的文檔,但是這樣做將失去XML已經實現的內容與格式無關(content-format?independenc)給我們帶來的所有便利。通常的,你可以寫一個XSLT樣式表,它能夠把XML源文檔轉換成XSL-FO。下面的列表2展示給我們的是一個XSLT樣式表。
列表2:一個用來轉換到XSL:FO的樣式表
<?xml?version="1.0"?>
<xsl:stylesheet?version="1.0"
??xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
??xmlns:fo="http://www.w3.org/1999/XSL/Format">
??<xsl:output?indent="yes"/>
??<xsl:template?match="/">
????<fo:root?xmlns:fo="http://www.w3.org/1999/XSL/Format">
??????<fo:layout-master-set>
????????<fo:simple-page-master?master-name="only">
??????????<fo:region-body/>
????????</fo:simple-page-master>
??????</fo:layout-master-set>
??????<fo:page-sequence?master-reference="only">
????????<fo:flow?flow-name="xsl-region-body">
??????????<xsl:apply-templates?select="http://ATOM"/>
????????</fo:flow>
??????</fo:page-sequence>
????</fo:root>
??</xsl:template>
??<xsl:template?match="ATOM">
????<fo:block?font-size="20pt"?font-family="serif"
??????????????line-height="30pt">
??????<xsl:value-of?select="NAME"/>
????</fo:block>
??</xsl:template>
</xsl:stylesheet>
利用WH2FO把Word文檔轉換成PDF?
作為本文的結束,將示例如何利用WH2FO把Word文檔轉換成PDF。示例所使用的Word就是這篇文章的Word初稿的前兩頁。
1.首先把Word2000/XP的文檔保存為htm文件。我使用的文件名是FOP.htm。
2.用WH2FO批處理命令轉換。我使用的WH2FO版本是0_3_1,Window系統。
>wh2fo?FOP.htm
該命令會產生三個文件:FOP.xml,FOP.xsl和FOPAtts.xsl。
3.下面將使用Apache?XML?Project中的FOP進行XSLT轉換和FOP渲染。我使用的FOP版本是fop-0.20.4rc。從Apache的FOP主頁http://xml.apache.org/fop/下載。它會引用到另外兩個jar包:avalon-framework和batik。
4.注冊FOP的漢字字體。FOP不直接支持TrueType字體,現在的版本可以通過注冊來解決。我注冊了常用的漢字字體。下面是黑體的注冊方法:
????*先生成一個xml的字體映射文件,由ttf(TrueType)字體文件產生:
????????>java?org.apache.fop.fonts.apps.TTFReader?C:\WINNT\Fonts\simhei.ttf?simhei.xml
?????對于ttc(包含多個TrueType)字體文件:
????????>java?org.apache.fop.fonts.apps.TTFReader?C:\WINNT\Fonts\simsun.ttc?-ttcname?"SimSun"?simsun.xml
*修改conf/userconfig.xml文件,在<fonts></fonts>中加入
?????<font?metrics-file="simhei.xml"?kerning="yes"?embed-file="c:\WINNT\fonts\simhei.ttf">
?????????<font-triplet?name="SimHei"?style="normal"?weight="normal"/>
?????</font>
5.XSLT轉換。在轉換前需要修改xsl文件,避免出現錯誤無法產生輸出。這些錯誤有些是因為xsl:fo版本不兼容或是目前FOP不支持的標記引起的,有些是不支持中文編碼引起的。
*修改FOPAtts.xsl文件:把中文“宋體”改成我們注冊的宋體,我用的名稱是“SimSun”;同樣的改“黑體”為“SimHei”。
*修改FOP.xsl文件:找到含有
<xsl:apply-templates??select="document('FOP.xml')/document/section[1?style='layout-grid:15.6pt']"></xsl:apply-templates>的行,把style='layout-grid:15.6pt'去掉。結果為:…section[1]…。轉換命令如下:
>org.apache.xalan.xslt.Process?-in?FOP.xml?-xsl?FOP.xsl?-out?FOP.fo
6.通過上面的步驟,我們將得到FOP.fo文件。再進一步轉換前,我們需要修改fo文件。改正它的頁面布局錯誤和語法不支持的錯誤。就像我們前面所將的一樣,你會發現這個生成的fo文件少了一個定義頁面的fo:simple-page-master元素(這種丟失頁面布局信息的情況有時候會出現)。我們使用的常規的A4頁面,所以可以在fo:layout-master-set節點下面這樣加上它:
*?<fo:simple-page-master?master-name="RegularA4"
??????????????????page-height="29.7cm"?
??????????????????page-width="21cm"
??????????????????margin-top="2.54cm"?
??????????????????margin-bottom="2.54cm"?
??????????????????margin-left="3.17cm"?
??????????????????margin-right="3.17cm">
??????<fo:region-body/>
<fo:region-before/>
??????<fo:region-after/>
</fo:simple-page-master>
????????*把fo:page-sequence-master下的子元素fo:repeatable-page-master-reference的master-reference屬性值改為我們剛才所定義的頁面名稱RegularA4。
????????*剩下的一個語法不支持錯誤:把fo:page-sequence的master-reference屬性改為fo:page-sequence-master的名稱”Section1-ps”。
7.最后運行下面的命令看結果吧:
>org.apache.fop.apps.Fop?-c?conf/userconfig.xml?FOP.fo?FOP.pdf

別忘了拷貝圖象文件的目錄。
下面是我做的結果,不能讓人十分滿意,尤其是有多種中文字體的時候。有的中文字顯示成“#”,這是由于FOP不支持中文粗體和斜體。但是無論如何它是一種技術,也許以后它會變得更完美。
?
參考1.APACHE?XML主頁http://xml.apache.org
2.W3C的XSL主頁http://www.w3.org/Style/XSL/
3.Fabio?Giannetti的主頁http://www-uk.hpl.hp.com/people/fabgia/index.html
4.XML?Bible?的第?18?章http://www.ibiblio.org/xml/books/bible2/chapters/ch18.html