XSLT问答:奇怪的转换
body{font-size:small;font-family:verdana}
pre,code{font-family: "courier new"}
pre {background-color:#e1e1e1}
XSLT问答:奇怪的转换
原作:John E. Simpson 2002.4.24 翻译:onestab
问:我的CDATA部分可以不被当作CDATA编码吗?(Can I un-CDATA my CDATA Section)
我在CDATA部分嵌入了一些HTML标签。(源文件不是我写的) 当我用XSLT将文件转换为HTML时,CDATA部分的标签比如<i> 到了浏览器中都变成了<i>。
有没有办法避免这种事情发生?
答:你没有提供文件例子,但我可以从你的描述推断你遇到的问题大致是这样的:
<true_xmlwrapper>
<![CDATA[
<html>
<head><title>Weird Embedded
Markup</title></head>
<body>
<h1>Someone thought he was being clever...!</h1>
<p><em>[etc.]</em></p>
</body>
</html>
]]>
</true_xmlwrapper>
我还可以假定你想要的转换结果应该是这样的:
<html>
<head><title>Weird Embedded Markup</title></head>
<body>
<h1>Someone thought he was being clever...!</h1>
<p><em>[etc.]</em></p>
</body>
</html>
如果我的推断不错的话,你转换所用的XSLT里大约应该有这样的一个模板:
<xsl:template match="true_xmlwrapper">
<xsl:value-of select="."/>
</xsl:template>
你可能已经发现,这解决了一个问题 -- 它只是去掉了开始和结束的<![CDATA[ ]]> 标记。 然而,写到结果树中的并非你想要的整洁的HTML代码,而是相当难看的:
<html>
<head><title>Weird Embedded
Markup</title></head>
<body>
<h1>Someone thought he was being clever...!</h1>
<p><em>[etc.]</em></em>
</body>
</html>
这种输出结果有些出乎我们的意料。实际的输出结果似乎在这样说:“下面这些文字中的尖括号不作为标记定界符对待,而是看成普通字符。”你猜到吗?这就是CDATA部分对那些标记敏感的字符的处理建议。文件的作者理所当然地认为他是在帮后续程序做件好事 -- 像这样把HTML标记裹在CDATA部分防止它被别的程序误读(就像这可恶的XSLT处理器一样)。事实上,CDATA内的这种包装就是要告诉任何明白标记语言的程序:“这看起来像标记语言,实际上不是,甚至也不是HTML”。这样一来XSLT处理器所做的假定当然是合情合理的。
原来如此。你可以这样试一试(在我的MSXML和Saxon XSLT处理器中是可行的):在你的XSLT样式表中,加入这个定级元素:
<xsl:output method="text"/>
这看起来似乎与直觉不符,甚至怪异。如果问题出在转换环节的输入端,那么指定输出特性有什么用?
如果文件中没有任何xsl:output元素,XSLT处理器就会试图根据转换后的结果树推测出样式表的用意,在进行推测时使用一系列测试,目的是判断结果树是不是HTML(缺省版本为HTML4.0, 不是XHTML);如果不是,就假定结果树是一个良构的XML普通解析实体(a well-formed XML general parsed entity)。(这个实体不一定是个良构的文件(document),比如,根节点可能含有两个子元素)对HTML结果树所进行的四项测试(必须全部通过)分别是:
结果树的根节点有一个子元素(即有一个根元素);
根元素的名称(不管其名称空间前缀)是"html";
作为根的 html 元素本身没有与任何的namespace URI相联系;并且
这个根元素之前的任何文本节点只能是空白的文本节点(whitespace-only text nodes)。
至于你所描述的那种文件的情形,这些测试几乎没有任何内容:乍看起来,它似乎包含有标记内容,但是无论怎么相似,实际上根据定义,一个CDATA部分只可能包含纯文本。这样在缺省情况下,在上述的结果树中没有“根元素”、html元素或其它任何东西。只不过是一个纯文本的字符串,而且它刚好以纯文本的 < 字符开始。这样结果树就没有通过HTML测试,处理器就猜测结果树只不过就是一个良构的一般解析实体,-- 在此处,它只包含有一个文本节点。
但是,如果指定了 method="text",你就跳过了处理器的缺省检测,告诉它不要对结果的类型做任何推测。
(使用这个小技巧有两个危险:首先,它是全局的 :你无法使它有选择地只作用于源/结果树中的某部分而不作用于其余部分;其次,也是更重要的,如果CDATA部分中的“标记”不是格式完好的,也将会被无条件地直接传到结果树。如果使用这个结果树的下游程序能读懂XML或HTML,则该下游程序将面临一场灾难。)
问:我的空白元素标签总是在末尾丢掉了一个空格。
为使我的XHTML能够兼容旧的浏览器(比如Netscape 4.77),在XSLT转换中我对空白的XHTML元素的结束斜杠前增加了一个空格,就像这样:
<xsl:template match="model/name">
<em>Model Name: </em>
<xsl:apply-templates/><br />
<!-- Note space ^ -->
</xsl:template>
然而,转换后的结果却是这个样子:
<em>Model Name: </em> Nimbus 2000<br/>
<!-- No space ^ -->
这对于新的浏览器来说没有问题,但是旧的浏览器不把<br/>当作<br>,只是忽略它,这样不太好。我看过一些关于如何在XML控制空白的技术资料(例如Bob DuCharme系列),但是这些资料都是针对元素内容,而不是元素标签本身的。我承认XML对空白的处理方式有它这么做的理由,那么看来从XML的角度试图控制一个标签内部 的空白似乎有些异想天开。到底有没有人知道如何在转换后用Perl脚本(修正它)的做法?
答:一个Perl 脚本?在转换之后?<冷颤/> 我的意思是,我喜欢Perl,但还是... 对付这个问题还是有几种方法的。
首先,还记得空白元素可以用一对连续的起始/结束标签表示,例如:
<br></br>
这样,你就可以把它放到结果树中,而不用它的空白标签形式,<br/>(不管斜杠其面有没有空格),这样做有一个问题,就是一些旧版本的浏览器会把它理解为连续的两个
br元素。
另外一种较好的解决方案是这个月本栏目第一个问题的变形。就像我上面所说的,XSLT处理器对结果树进行有意识的猜测。我不明白为什么它认不出你的结果树是HTML4.0(新旧浏览器都可读懂)。但是你可以用这个顶级元素规定处理器的翻译:
<xsl:output method="html"/>
例如,在这种情况下,当你的样式表中含有XML兼容的<br/>标签(有无空白皆可),兼容的处理器就会以HTML兼容的<br>形式输出。
我想我的介绍对你的问题能有些启发;它强制地规定结果树不是XHTML,该怪罪的是愚笨的HTML4.0,不幸的是我们正处于浏览器和XHTML发展的过渡阶段,如果是我的话,我就会利用新浏览器的容忍性,而不是按照XHTML的
严格要求写代码,但愿旧的浏览器的某些表现能如所愿。(浏览器在设计时往往不会遵从标准,也难怪它们对较新的标准的支持更加虚弱。)