XSLT问答:转换经验
XSLT问答:转换经验
原作:John E. Simpson 2002.05.29翻译: onestab
问:可以将XSL格式化对象(XSL-FO)转换成HTML吗?
我有一个XSL-FO文件,它使用FOP显示PDF文件,我能用同一个XSL-FO文件显示到XHTML吗?如果可以,要使用什么工具?
答: 先看第二个问题:我想可以从RenderX开发的 fo2html XSLT stylesheet着手。该样式表使用一个XSLT-FO文件将XSL格式化对象在某种程度上转换成相应的XHTML,使用CSS1进行格式化。不过要留意其局限性。
RenderX是XEP XSL-FO引擎的开发者,是Apache FOP的主要对手,这个样式表是该产品的一个“附加件(add-on)”。但是这个样式表应该对其它的XSL-FO输入同样适用。另外,尽管上述RenderX页面主要针对在能读懂XSLT的浏览器中直接使用样式表,我想没有理由不可以方便地用于任何XSLT引擎,包括Saxon, Cocoon或任何别的地方。
至于要把XSL-FO转换成XHTML要用到的技术,这不是三言两语能讲清的,容我慢慢道来。
XSL-FO是一个XML词汇表,其元素和属性描述了打印文档的结构。对这类输出格式的要求与主要用于Web浏览的输出格式(比如XHTML)的要求有着很大不同。一个网站的内容一般都是散落于多个分离的文件,而且在大多数情形下并不需要打印出来。
- (页面作者)无法要求一个Web页面在指定尺寸的纸张上打印;
- 某个部分的标题可能会出现在页面底部,其相应内容却出现在下一页的顶端;
- 字体和字号以及页边距最终还是要受用户的控制;
- 用户可能有意关闭图片显示;
- 页眉和页脚浏览器有缺省设置,可被全部重置或忽略,并且这些页眉和页脚的外观完全不是页面设计者的所能控制的;
- 一些支持材料,比如脚注和索引,可以被重新构造 -- 但是结果可能很糟糕;
- 如果一个跨页面的表格被打印到下一页,其标题单元格不会重复...
这里列出的差别用一句话来形容,就是非常巨大。
尽管如此,相似之处还是有的。例如,fo:block
格式化对象就相当于XHTML的div
,fo:table
对象很像XHTML的 table
元素;XHTML中的 ul
和 li
元素与XSL-FO中的 fo:list-block
和 fo:list-item
相对应,等等。
将XSL格式化对象自动转化成相应的XHTML的一般过程取决于从一个XML词汇表(XSL-FO)到另一个词汇表(XHTML)的逐条转换。这正是XSLT的本职工作;RenderX的fo2html方案也不例外。事情变得棘手了,我们处理的这些打印页面特性在Web页面里的相应(对象)不很准确,或更糟糕 - 干脆没有。有些东西你会转而寻求CSS的帮助。即使用了CSS,但你始终不可能在XSL-FO(或其他功能齐备的页面描述语言)和XHTML之间建立完整的一一对应关系。
需要指出,有人也许会过分抱怨RenderX方案的不足:它所产生的CSS样式指令都是作为div
元素的style
属性出现的。为达到较强的控制能力和一致性,有人可能更愿意从一个XSL-FO元素能产生两个输出结果:一个XHTML文档和一个分离的、可选的CSS样式表。RenderX不这样做不是技术欠缺,而是因为:
- 他们对fo2html进行尝试的指导思想是在浏览器中直接察看XSL-FO文档,需要迅速转换成XHTML文档;以及
- XSLT1.0样式表无论如何都不能一次产生一个以上的结果树(即“输出”文档)。(XSLT2.0正式发布时,可能允许创建多个“二级结果树”,所用的指令为
xsl:result-document
。)
问:怎样用XSLT将一个“标签(tag)”放入一个属性内?
在我的XML文件中,有一个叫做 filmid
的标签。这里是例子:
<filmid>l</&filmid>
在XSLT样式表中,我想把它转换成 <img src>
标签内的文件名,就像这样:
<img src="images/<xsl:value-of select="filmid"/>.gif" />
但却行不通。
答:我想你可能已不再考虑这些方法了,但这个问题并不是XSLT的局限性。问题是你对XSLT的理解有限。别不好意思;在这个问题面前你还有很多同志。
这里的关键是:XSLT并不会在输出文件中写入或创建一个 标签;它在结果树中创建的是元素(以及属性、PI等等)。 也就是说, 样式表并不是由这样的一系列指令组成的:
- 写一个
img
的起始标记 - 如果你(XSLT处理器)找到了一条XSLT指令(诸如
xsl:value-of
之类),不管还剩下什么,停下标签的写入,按照指令去做 - 写入
img
的结束标记
相反,构成样式表的是一系列XSLT引擎指令:
- 创建一个
img
元素 - 为该元素创建一个
src
属性
注意这里没有提到“标签”这个词,也没有关于写入或创建元素的所谓“起始”或“结束”标签(或别的之类)。事实上,XSLT引擎依据这些指令所创建的东西与markup毫无关系。不难想象一个懂得XSLT的程序可以用这些样式表指令产生一个文档结构的树形图,可以是SVG文件,也可以是PostScript,TIF或PNG。
再者,要知道样式表被提交给XSLT引擎之前,它本身还要过XML解析器这一关。不管充满“指令”的样式表看起来如何顺眼,假如这个样式表本身连一个良构的(well-formed)XML都算不上,则这些指令本身是不是合法的XSLT,根本就无关紧要。
根据记录,对任何一个负责任的解析器来说,你所提供的XSLT代码片断根本就不符合它的接纳条件,肯定会出现一两个错误。首先,解析器试图把 src
属性的值理解为一个字符串,这个字符串在双引号内,而其中又包含双引号,解析器对此无法理解。为纠正这个错误,你应把"filmid"外面的双引号换成单引号,就像这样:
<img src="images/<xsl:value-of select='filmid'/>.gif" />
或者,保留"filmid"外面的双引号,而把整个属性值外面的双引号改成单引号。
尽管如此,解析器还是会被噎住,因为属性值不可以包含特殊字符(unescaped)小于号(<
)。但是让我们设想你用的解析器非常宽宏大量(而且不兼容),马马虎虎接受了你指定的 src
属性值,那么从你的样式表能得到什么样的结果?你得到了一个img
元素,其属性值就是双引号内的所有文字。也就是说,XSLT引擎不知道你喂给它的是一条 xsl:value-of
指令!它会简单地认为 src
属性值的组成就是 i
, 接着是 a
,直到第一个斜杠,跟着是一个小于号,接下来是 x
, s
, 以及l
和冒号,等等。
假如你把小于号用一个实体引用脱出,比如"
XSLT的行为就跟上述一样。
解决这个问题有两种办法。最简单的(至少在这种情况下)就是忘掉 xsl:value-of
元素,转而使用所谓的属性值模板(通常缩写为AVT)。一个AVT由一个包含在花括号({ }
)内的XPath表达式组成。如果这个表达式是一个相对局部路径,它就是相对于当前点的上下文的。例如,
<img src="images/{filmid}.gif" />
XSLT引擎一定会对花括号的存在做出这样的反应:“在该点插入当前上下文(context)节点集的filmid
子元素的字符值”
另外一种可行的办法写起来比较长一些,但对于大多数的XSLT程序员来说非常明白,不是什么编程秘籍,(有时候由于某种原因不能使用AVT,这种方法依然可行)。这个办法就是用 xsl:attribute
元素将一个属性添加到节点树中,这个元素的字符值,可能包含有xsl:value-of
,就成为问题中的属性值。
<img><xsl:attribute name="src">images/<xsl:value-of select='filmid' />.gif</xsl:attribute></img>
这样就为紧接其前面元素(这里是img
元素)增加了一个属性,显然,属性的名称是由name
属性指定的,并且你已看到,属性之可以由纯文本和XSLT指令组成。
不管你用的是AVT还是 xsl:attribute
元素,结果中的相应部分不外乎下面两种之一:
<img src="images/1.gif"/> <img src="images/1.gig"></img>
这两种结果都是结构完备的,而且都代表一个空的 img
元素。