术语
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="//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