XSL 转换:XSLT 可缓和 XML 架构的不相容性问题
发布日期: 4/14/2004 | 更新日期: 4/14/2004
Don Box,Aaron Skonnard,John Lam 本文假设您熟悉 XML 和 XPath。
本文假设您熟悉 XML 和 XPath。
下载本文代码: Box0800.exe (32 KB)
摘要:由特邀作者 Benjamin Guralnik 介绍反向链接的概念。
摘要XSL 转换 (XSLT) 规范定义了一种基于 XML 的语言,用于表达将一个 XML 文档映射到另一个文档的转换规则。XSLT 具有许多传统编程语言中的构造函数,包括变量、函数、迭代和条件语句。在本文中,您将了解如何使用 XSLT 指令和模板规则、管理命名空间、控制转换输出、使用多个样式表,以及使用模板规则来进行模式匹配。文中还说明了如何通过 IXSLTemplate 和 IXSLProcessor 接口从 MSXML 访问 XSLT。
本文选自即将出版的书籍 Essential XML(第五章),由 Don Box、Aaron Skonnard 和 John Lam 编著,? 2001 年 Addison Wesley Longman。未经 Addison Wesley Longman 允许,不得复制。保留所有权利。
定义语言已经成为描述 XML 文档类型和结构的主要方法。XML 架构为构建基于 XML 的交互操作系统提供了底层基础架构,这是因为它们提供了一种描述 XML 的通用语言,该语言是根据公认的软件工程学原理设计而成的。这就是说,XML 架构的表达方式可让对同一组领域特定的抽象问题进行建模的多个组织提供不同的架构文档。当然,这个问题可以通过为每个领域定义规范架构的业界公会来解决,但是在那之前,处理相同基本信息的多个架构定义是无法更改的事实。进入 XSL 转换 (XSLT)。
XSLT 规范定义了一种基于 XML 的语言,用于表达将一类 XML 文档转换成另一类时所使用的转换规则。XSLT 语言可被看作编程语言,现在至少有两种 XSLT 执行引擎可以将 XSLT 文档当作程序直接运行。除此之外,将 XSLT 文档作为通用目的语言来表达从一种架构类型到另一种类型的转换也是很有用的。实际上,我们可以将 XSLT 文档想像成为任意 XML 转换引擎的一种输入形式。
XSLT 擅长将一种基于 XML 的表示形式映射为另一种表示形式。考虑图 1 中显示的 XML 文档(在我们的讨论中会多次引用该文档)请注意,命名空间(和架构)中的元素名称是由 Addison Wesley Longman(本书的出版商)定义的。现在让我们看一下相同信息的第二种表示形式:
<?xml version="1.0"?>
<content xmlns="http://www.develop.com/Schemas/book">
<name>Essential XML</name>
<contributors>
<staff principal="true">Don Box</staff>
<staff>Aaron Skonnard</staff>
<staff>John Lam</staff>
</contributors>
</content>
这次,命名空间(和架构)中的元素名称是由 DevelopMentor(作者所在地)定义的。
上面所示的两个文档基本上包含相同的信息。但是,外表常常具有欺骗性。假如没有人为干预,就不可能通过算法来确定两个底层架构之间是否存在任何联系,即使它们的局部元素和属性使用类似或完全相同的名称。如果能够理解这两个架构语义的人员认为确实存在某种联系,则使用一种语言来描述两个架构实例之间的转换是非常有用的。
描述这些转换的一种简单方法就是用传统的编程语言来编写代码。图 2 展示了使用 Java 语言编写的一个基于文档对象模型 (DOM) 的程序示例,它可以将上述第一个文档翻译为第二个文档。尽管这个程序是有效的,但是它只能被虚拟机和(假设提供有源代码)那些了解 Java 语言代码的人所理解。此外,这个程序非常脆弱,且需要进行大量修改才能追踪源架构和目标架构的独立演变。
相反,请考虑图 3 中的 XSLT 文档,它完成了与前一个 Java 语言程序相同的任务。这个 XML 文档同时反映了源架构和目标架构,并且可以交给 XSLT 处理器将源架构的实例转换为目标架构的实例。如此处所示,架构转换是通过根据对源架构的更改来实现目标架构样本的方式来描述的。更重要的是,这个文档可以用标准 XML 解析器解读,并且可以用作多种处理软件(而不仅仅是 XSLT 转换引擎)的输入。
如图 3 所示,仅使用 XML 还不能保证互用性。尽管人们已经认同了尖括号的作用,但是用它来解释所有信息还是一个亟待解决的问题。XML 架构为您提供了表达类型定义的工具,但是由于缺少通用架构,在不同组织间建立连接桥梁仍然需要人为干预。XSLT 提供了以 XML 为中心建立桥梁的方法。
XSLT 基础
XSLT 是一种基于 XML 的语言,它描述了从 XML 文档到任意文本格式(可能是 XML,也可能不是 XML)的转换。如图 4 所示,XSLT 假定有三个文档参与:源文档、XSLT 样式表文档和结果文档。源文档只是一个为转换提供输入的正规 XML 文档。样式表文档是一个使用 XSLT 词汇表达转换规则的 XML 文档。结果文档是一个文本文档,它是按照 XSLT 样式表中描述的转换来运行源文档时产生的。
图 4 XSLT 转换过程
XSLT 样式表包含一个或多个 XSLT 模板。模板是文本结果元素和 XSLT 指令的集合。文本结果元素是被或多或少地逐字复制到结果文档的元素。XSLT 指令是大家都很熟悉的元素,用于改变模板的处理过程。>图 5 展示了完整的 XSLT 指令列表。XSLT 指令和其他构造函数始终由 XSLT 命名空间 URI (http://www.w3.org/1999/XSL/Transform) 限定,该 URI 通常对应的命名空间前缀是 xsl。
XSLT 样式表可被写成单一模板的文本结果元素,或写成显式样式表。前者只是目标架构的样本,其中添加了命名空间限定的 XSLT 指令。后者是特定的 XSLT 文档,它的根元素是 xsl:stylesheet。显式样式表稍后会在本文的“基于模板的编程”一节中加以讨论。
基于文本结果元素的样式表支持更灵活、更富有表达力的 xsl:stylesheet 词汇的子集。以下是最简单的以文本结果元素撰写的 XSLT 样式表:
<?xml version='1.0' ?>
<doc>Hello, World</doc>
这个样式表能够产生与源文档无关的相同结果文档。与 Kernighan 和 Richie 的经典程序一样,这个版本毫无用处,不论输入是什么,它的输出结果都是一样的。
包含源文档内容的最简单方法是使用 xsl:copy-of 指令。这个指令的运行方式和 XInclude 的包含元素(类似外部解析实体)类似。主要区别是 xsl:copy-of 被整合进 XSLT 使用的 XPath 上下文环境。
请参看以下 XSLT 文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'
><xsl:copy-of select='/book/author'/></doc>
XSLT 的规则指出:xsl:copy-of 元素将由 select XPath 表达式产生的节点集替换。假设提供以下源文档作为输入
<?xml version="1.0"?>
<book title="Essential XML" >
<author name='Don' age='25' canadian='false'/>
<author name='Aaron' age='19' canadian='false'/>
<author name='John' age='20' canadian='true'/>
</book>
结果文档将类似以下内容:
<?xml version='1.0' ?>
<doc>
<author name='Don' age='25' canadian='false'/>
<author name='Aaron' age='19' canadian='false'/>
<author name='John' age='20' canadian='true'/>
</doc>
xsl:copy-of 指令最适用于复制节点集,但是用它来复制源文档的文本则有点麻烦。为此,XSLT 提供了两种机制:其一,将源文本作为 attribute [children] 包含;其二,将源文本作为 element [children] 包含。
文本结果元素的所有属性均被解释为属性值模板,这些模板只是包含经评估以产生结果文本的嵌入式 XPath 表达式的字符串。当 { 和 } 字符作为 attribute [children] 出现时,XSLT 会对它们进行特殊处理。遇到 { 字符时,其后的字符(到 ) 为止}均被解释为字符串值的 XPath 表达式。例如,如果在文本结果元素中显示以下属性
bookname='The book is {/book/@title}'
结果文档中相应的属性是:
bookname='The book is Essential XML'
要将 attribute [children] 中的 { 和 } 字符转义,您必须使用多余的 { 和 } 字符来说明不存在属性值模板。例如,文本结果元素的以下属性
curlies='{{}}'
在结果文档中会扩展为:
curlies='{}'
属性值模板也可以应用在 XSLT 指令的小型子集上。(在 XSLT 规范或图 5 的图表中,您可以通过观察指令和属性的语法规范两边是否有{ } 来立即确定给定的 XSLT 指令属性是否接受了属性值模板。)
当 { 和 } 作为 attribute [children] 出现时才会受到特殊处理。要将源文本作为 element [children] 包含,您必须使用 xsl:value-of 指令。该指令与 xsl:copy-of 类似,主要区别是 xsl:value-of select 表达式会在替换之前被转换为字符串。
请参看以下文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'
><xsl:value-of select='/book'/></doc>
这个 XSLT 样式表表明:doc 元素的内容应由对源文档运行 XPath 表达式 /book 时生成的文本组成。由于 select 表达式是一个节点集,系统将使用 XPath 转换规则,在插入前将其隐式转换为字符串。以下是与该 XSLT 样式表对应的结果文档:
<?xml version='1.0' ?>
<doc/>
请注意,由于源文档未包含 element [children] 形式的字符数据,所以 select 表达式 /book 产生了空字符串。如果 select 表达式是 /book/author/@name,结果文档将是:
<?xml version='1.0' ?>
<doc>Don</doc>
这是因为节点集到字符串的转换规则规定第一个节点应转换为字符串值。
xsl:copy-of 和相关指令是从源文档导入内容的简单机制。XSLT 还提供了一组条件计算指令,这些指令对于从事程序设计语言的开发人员来说是非常熟悉的。xsl:if 指令相当于 C++ 和 Java 语言中的 if 语句。xsl:choose、xsl:when 和 xsl:otherwise 指令相当于 C++ 和 Java 语言中的 switch、case 和 default 命令。
当然,最简单的莫过于 xsl:if 指令了。一个表述 xsl:if 指令的元素必须含有包含布尔 XPath 表达式的测试属性。如果该表达式的计算结果为 true,则 xsl:if 元素的 [children](它本身是一个 XSLT 模板)将被处理。如果该表达式的计算结果为 false,则 xsl:if 元素的 [children] 将被忽略。
请参看以下 XSLT 文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'>
<xsl:if test='count(//author) > 4'>
<cacophony/>
</xsl:if>
</doc>
此 XSLT样式表使用 xsl:if 指令来测试 author 元素的数量。如果源文档有四个以上的 author 元素,结果文档将为:
<?xml version='1.0' ?>
<doc><cacophony/></doc>
如果源文档有四个或以下的 author 元素,结果文档将为:
<?xml version='1.0' ?>
<doc/>
请注意,测试属性中使用的 XPath 表达式是一个类似于 XPath 预测的布尔表达式,而不是完整的 XPath 位置路径。
xsl:choose 指令提供了 Java 或 C++ 语言的 switch 语句的功能。xsl:choose 指令包含一个或多个 xsl:when 从句和一个可选的 xsl:otherwise 从句。xsl:when 和 xsl:otherwise 元素表示为所包含 xsl:choose 元素的 [children]。每个 xsl:when 元素都可以含有一个包含布尔 XPath 表达式的测试属性。与 Java 或 C++ 语言的 switch 语句不同的是,可能会有多个 xsl:when 从句的测试结果为 true 。要解决这种多个从句结果为 true 的问题,XSLT 仅允许处理测试成功的第一个 xsl:when 从句。
请参看以下 XSLT 文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'>
<xsl:choose>
<xsl:when test='count(//author) = 1'>
<soloist/>
</xsl:when>
<xsl:when test='count(//author) < 5'>
<ensemble/>
</xsl:when>
<xsl:otherwise>
<cacophony/>
</xsl:otherwise>
</xsl:choose>
</doc>
如果源文档只有一个 author 元素,结果文档将为:
<?xml version='1.0' ?>
<doc><soloist/></doc>
如果源文档有 2 到 4 个 author 元素(或者根本没有 author 元素),结果文档将为:
<?xml version='1.0' ?>
<doc><ensemble/></doc>
在其他任何情况下,结果文档为:
<?xml version='1.0' ?>
<doc><cacophony/></doc>
请注意,xsl:otherwise 没有判断从句,它相当于 Java 或 C++ 语言的 switch 语句中的 default 从句。同时也应注意,xsl:when 从句的顺序很重要。如果两个 xsl:when 从句的顺序颠倒,将不会输出 <soloist/> 元素,例如表达式
count(//author) = 1
将被前面的表达式屏蔽
count(//author) < 5
因为 1 小于 5。
重复和循环指令
除条件指令之外,XSLT 还提供了重复和循环指令。这些指令中最基本的是 xsl:for-each,它依赖 XPath 表达式来产生控制循环迭代的节点集。当遇到 xsl:for-each 元素时,系统将计算其 select 属性中的 XPath 表达式。结果节点集中的每个节点均被插入 xsl:for-each 元素的 [children],以进行进一步地处理。
请参看以下 XSLT 文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'>
<xsl:for-each select='/book/author'>
<by/>
</xsl:for-each>
</doc>
假设源文档是图 1 中的文档,将产生以下结果文档:
<?xml version='1.0' ?>
<doc><by/><by/><by/></doc>
这个 xsl:for-each 示例没有什么实际意义,因为它的内容与节点集中选中的节点无关。现在进一步解释通过 xsl:for-each 指令访问其他内容。
XSLT 样式表中的 XPath 表达式是根据上下文环境进行计算的。该上下文环境至少由一个 XPath 节点和节点集组成。对于绝对位置路径来说,上下文环境几乎没有意义。而对于相对位置路径来说,上下文环境决定很多内容。例如,以下 XSLT 指令就要求有一些标记来表明涉及到的节点以及这些节点所属的节点集:
<xsl:value-of select='position()' />
在讨论 xsl:for-each 指令之前的示例中,上下文节点是源文档的根节点,上下文节点集是仅包含根节点的集合。但是某些 XSLT 构造函数能够改变用于计算 XPath 表达式的上下文环境。xsl:for-each 指令就是这样一种指令。
xsl:for-each 指令能够改变其 [children] 中所有 XPath 表达式的上下文环境。上下文节点集是从 select 表达式返回的节点集。上下文节点会随每个循环迭代而改变。对于循环的第 n 个迭代,上下文节点就是上下文节点集中的第 n 个节点。
例如,参看以下 XSLT 文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'>
<xsl:for-each select='/book/author'>
<by id='{position()}'>
<xsl:value-of select='@name' />
</by>
</xsl:for-each>
</doc>
出现在 xsl:for-each 指令中的两个 XPath 表达式:position() 和 @name,将根据 select 表达式产生的节点集中的当前节点进行计算。假定使用图 1 中的 XSLT 样式表和源文档,结果文档将为:
<?xml version='1.0' ?>
<doc><by id='1'>Don</by><by id='2'>Aaron</by><by id='3'
>John</by></doc>
请注意,在此例中,由 xsl:for-each select 表达式产生的节点集包含了源文档中的所有 author 元素。
默认情况下,xsl:for-each 指令将按照文档中的顺序遍历整个节点集。您可以使用 xsl:sort 指令更改这种行为方式。xsl:sort 指令元素必须以 xsl:for-each 元素的起始 [children] 形式出现,并且必须遵循以下语法:
<xsl:sort
select = string-expression
lang = { nmtoken }
data-type = { "text" | "number" | qname-but-not-ncname }
order = { "ascending" | "descending" }
case-order = { "upper-first" | "lower-first" } />
最重要的属性是 select 属性,它指定的 XPath 表达式会用作节点集的排序关键字。在没有 select 属性的情况下,XSLT 处理器会将“.”作为 select 表达式。
要查看起作用的 xsl:sort,请参看以下 XSLT 文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'>
<xsl:for-each select='/book/author'>
<xsl:sort select='@name' />
<by><xsl:value-of select='@name' /></by>
</xsl:for-each>
</doc>
若源文档通过这个 XSLT 样式表插入,将产生以下结果文档:
<?xml version='1.0' ?>
<doc><by>Aaron</by><by>Don</by><by>John</by></doc>
值得注意的是,由 XPath 表达式 /book/author 产生的节点集是按照名称属性进行排序的。假定使用以下 xsl:sort 指令
<xsl:sort select='@age' order='descending'
data-type='number' />
节点集应是根据年龄属性按降序排列的,结果文档是:
<?xml version='1.0' ?>
<doc><by>Don</by><by>John</by><by>Aaron</by></doc>
数据类型属性控制如何转译值空间的排序。有两个内置常量:text 和 number,其含义也正如它们的名字一样。此外,XSLT 还支持引用 XML 架构数据类型的 Qname,但是在撰写此文时,还没有处理器能够支持基于架构类型的排序。
xsl:sort 指令还可以用于为节点集指定多个排序关键字。在 xsl:for-each 指令内容中遇到的第一个 xsl:sort 指令会被视为排序主关键字。后面的 xsl:sort 指令会被视为排序次关键字。例如,请参看以下 XSLT 文本结果元素:
<?xml version='1.0' ?>
<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xsl:version='1.0'>
<xsl:for-each select='/book/author'>
<xsl:sort select='@canadian' order='descending'/>
<xsl:sort select='@name' />
<by><xsl:value-of select='@name' /></by>
</xsl:for-each>
</doc>
由于 xsl:for-each 指令进行了多关键字排序,此 XSLT 将产生以下结果:
<?xml version='1.0' ?>
<doc><by>John</by><by>Aaron</by><by>Don</by></doc>
由于 canadian 属性由第一个 xsl:sort 指令使用,它决定了整个排序顺序。
基于模板的编程
到现在为止,我们一直使用文本结果元素作为样式表。这对于简单转换是很有效的,但由于其线性结构,不可能将其模块化为更小的可重用 XSLT 块。由于这个原因,大多数较为复杂的 XSLT 样式表均不使用文本结果元素作为样式表,而是使用显式 xsl:stylesheet 格式。
xsl:stylesheet 主要由一个或多个模板规则组成。模板规则在 XSLT 中起到函数的作用,且总是显示为 xsl:stylesheet 元素的顶层 [children]。模板规则可将 QName 或模式绑定到模板。正如我们先前提及的,模板是文本结果元素和 XSLT 指令的组合。QName 仅仅是一个与模板相关联的符号名称。以下是一个已命名的 XSLT 模板规则:
<xsl:template name='emitSignature' >
<sig><xsl:value-of select='/book/@title'/></sig>
</xsl:template>
可使用 xsl:call-template 指令从其他模板中调用此模板规则:
<xsl:template name='enchilada' >
<doc>
<xsl:call-template name='emitSignature' />
</doc>
</xsl:template>
假定使用图 1 中的源文档,enchilada 模板规则将产生以下结果:
<doc><sig>Essential XML</sig></doc>
要调用 enchilada 模板规则,您只需在样式表的任意位置上使用 xsl:call-template 指令即可。
与函数一样,模板规则可以通过参数调用。要支持参数,模板规则的 [children] 应以一个或多个 xsl:param 指令开头,这些指令会声明已命名的参数并设置其默认值。所有模板参数都会添加到模板的上下文环境中,并可通过在参数名称前加 $ 以由 XPath 表达式引用,如 $arg1。xsl:param 指令的语法定义如下:
<xsl:param
name = qname
select = expression>
<!-- Content: template -->
</xsl:param>
参数声明可使用 XPath 表达式或模板作为其 [children] 来设置参数的默认值。
<xsl:template name='emitTop' >
<xsl:param name='arg1' select='/book/author[2]/@name' />
<xsl:param name='arg2' >true</xsl:param>
<top>
<xsl:if test='$arg2' >
<sometimes/>
</xsl:if>
<one><xsl:value-of select='$arg1' /></one>
</top>
</xsl:template>
以下命名的模板规则声明并使用了两个模板参数:
如果模板规则被调用时没有参数,则会根据 select 属性或每个 xsl:param 指令的 [children] 来派生默认值。假定使用图 1 中的源文档,将产生以下结果树片段:
<top>
<sometimes/>
<one>Aaron</one>
</top>
要调用使用参数的模板规则,只需提供一个或多个 xsl:with-param 指令作为 xsl:call-template 指令的 [children] 即可。
<xsl:call-template name='emitTop' >
<xsl:with-param name='arg1' >Hello</xsl:with-param>
<xsl:with-param name='arg2' select='false()' />
</xsl:call-template>
除元素名称之外,xsl:with-param 指令的语法也与 xsl:param 指令的语法是完全相同。假定这样调用 emitTop,将产生以下结果树片段:
<top>
<one>Hello</one>
</top>
请注意,由于 arg2 计算后的布尔值为 false,所以 xsl:if 指令的 [children] 不会进行计算。
xsl:param 元素通常显示为模板规则的 [children]。XSLT 还允许样式表本身接受参数。这些样式表级别的参数声明必须以 xsl:stylesheet 元素的 [children] 形式出现。至于 XSLT 处理器初始化这些参数的方式,则取决于处理器自身。补充内容“从 MSXML 使用 XSLT”说明了 MSXML 处理样式表级别参数的方法。
除命名的参数之外,XSLT 还可通过 xsl:variable 指令支持命名的变量。除元素名称外,xsl:variable 指令的语法与 xsl:param 的语法完全相同。xsl:param 和 xsl:variable 的之间的区别是:通过使用 xsl:with-param,可在调用模板时重写参数的初始值,而变量则不可以。此外,xsl:param 指令必须出现在包含它们的模板的顶部;而 xsl:variable 指令可以出现在指令允许的任何位置。不论何种情况,给定的变量名或参数名称仅可在每个模板中定义一次。定义为 xsl:stylesheet 元素 [children] 的变量和参数具有全局作用域,并在所有模板中可见,但模板可以通过定义相同名称的变量或参数来隐藏全局定义。因此,以下是一个合法的 XSLT 样式表:
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:param name='bob' select='2' />
<xsl:template name='steve'>
<xsl:variable name='bob' select='4'/>
</xsl:template>
</xsl:stylesheet>
但以下是不合法的样式表,因为名称 bob 在同一模板中定义了多次:
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template name='steve'>
<xsl:param name='bob' select='2' />
<xsl:variable name='bob' select='4'/>
</xsl:template>
</xsl:stylesheet>
应该意识到,一旦定义了变量或参数的值,将没有办法进行修改。这是因为 XSLT 是一种功能性编程语言,而不是 C++ 或 Java 这样的命令性语言。与 C++ 或 Java 语言的函数不同,XSLT 模板不会产生副作用。并且,它们只能产生作为整体样式表输出或作为其他模板输入的结果。部分原因是为了简化 XSLT 处理模型。另一个原因是为了支持模板规则的并行执行或无序执行,因为可以保证执行一个规则不会影响到其他规则的执行。
命名空间和 XSLT
除了 XSLT 指令和构造函数与 XSLT 命名空间的必需从属关系外,本文中使用的示例源文档和样式表文档尚未使用命名空间。这样做的目的是可使文本结果元素和 XPath 表达式更加紧凑并具可读性。一般来说,这样做是不现实的,因为大多数实用的 XML 文档都非常依赖于命名空间。
回想一下那些出现在 XPath 表达式中不带前缀的 Qname,它们均被假定为与命名空间没有从属关系。如果不考虑可能已生效的默认命名空间声明,情况确实如此。相反,出现在 XPath 表达式中带有前缀的 QName 总是会在用于 NodeTest 或其他 QName-aware 结构之前先展开。当 XPath 表达式作为 XSLT 元素的属性值出现时,XSLT 会将 [in-scope namespace declarations] 属性中的声明(作用域内可能没有任何默认的命名空间声明)添加到 XPath 计算上下文环境中。
处理文本结果元素时,所有映射到 http://www.w3.org/1999/XSL/Transform 的命名空间声明都会从结果中被去除。(当文本结果元素作为样式表使用时,所使用的所有 XSLT 特定的属性,如 xsl:version,在转换过程中也会被除去。)但是,出现在文本结果元素中的所有其他命名空间声明都被视作重要内容,并会出现在结果文档中。您可以使用 xsl:exclude-result-prefixes 属性来取消此行为。该属性可以出现在样式表文档的根元素中,并可以包含以空格分隔的命名空间前缀列表。前缀出现在该列表中的命名空间声明将不会出现在结果文档中。
例如,以下是一个使用命名空间的 XSLT 文本结果元素:
<?xml version='1.0' ?>
<dm:out xmlns='http://example.com'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:dm=' http://www.develop.com/Schemas/doc'
xmlns:awl='http://awl.com'
xsl:version='1.0'
><xsl:value-of select='/awl:product/@title'/></dm:out>
如下所示,计算此样式表后将产生以下输出:
<?xml version='1.0' ?>
<dm:out xmlns='http://example.com'
xmlns:dm='http://www.develop.com/Schemas/doc'
xmlns:awl='http://awl.com'
>Essential XML</dm:out>
因为 awl 命名空间声明仅作为模板中 XPath 表达式的上下文环境存在,所以它在结果文档中是多余内容。要取消这种不必要的命名空间声明,可按以下方式撰写文本结果元素:
<?xml version='1.0' ?>
<dm:out xmlns='http://example.com'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:dm=' http://www.develop.com/Schemas/doc'
xmlns:awl='http://awl.com'
xsl:exclude-result-prefixes='awl'
xsl:version='1.0'
><xsl:value-of select='/awl:book/@title'/></dm:out>
这个样式表应产生不包含 awl 命名空间声明的结果文档:
<?xml version='1.0' ?>
<dm:out xmlns='http://example.com
xmlns:dm='http://www.develop.com/Schemas/doc'
>Essential XML</dm:out>
要同时排除默认命名空间声明,请在排除的前缀列表中加入伪前缀#default。若上一示例中的 exclude-result-prefixes 属性为
xsl:exclude-result-prefixes='awl #default'
结果文档将为:
<?xml version='1.0' ?>
<dm:out xmlns:dm='http://www.develop.com/Schemas/doc'
>Essential XML</dm:out>
但是,还有两个问题。首先,xsl:stylesheet 元素可能不会作为 xsl:template 的 [children] 合法出现。其次,绑定 XSLT 命名空间 URI 的所有命名空间声明将在结果文档中被取消。这两个问题都可由 xsl:namespace-alias 来解决。
xsl:namespace-alias 指令可改变一个命名空间声明的命名空间 URI,方法是将此命名空间声明与第二个可提供要在结果文档中使用的实际命名空间 URI 的命名空间声明相关联。 xsl:namespace-alias 的语法如下:
<xsl:namespace-alias
stylesheet-prefix = prefix | "#default"
result-prefix = prefix | "#default"
/>
result-prefix 表明包含将在结果文档中出现的实际命名空间 URI 的命名空间声明。stylesheet-prefix 表明应在发布结果文档之前重声明的命名空间声明。
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template name='emitStylesheet' >
<xsl:stylesheet version='1.0'>
<xsl:template name='{//@procName}' />
</xsl:stylesheet>
</xsl:template>
</xsl:stylesheet>
请参看以下样式表文档:
由于 emitStylesheet 模板的 [children] 与 XSLT 命名空间不再有从属关系,它们被视为文本结果元素。但是,urn:fake:uri 命名空间 URI 的所有命名空间声明将被转换为 http://www.w3.org/1999/XSL/Transform 命名空间 URI 的声明。这意味着,emitStylesheet 模板将产生以下结果文档:
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template name='func2' />
</xsl:stylesheet>
如果 xsl:namespace-alias 指令未在样式表中出现,结果文档将为:
<xsl:namespace-alias
stylesheet-prefix = prefix | "#default"
result-prefix = prefix | "#default"
/>
由于可以将命名空间别名用于其他命名空间,XSLT 命名空间 URI 最常使用此指令。
生成的输出
有一类转换是不能使用文本结果元素来实现的。如果元素或属性的局部名称或命名空间 URI 需要根据 XSLT 处理进行计算,则不能使用文本结果元素。使用文本结果元素时,有条件地发出属性也比较复杂。除此之外,模板中出现的任何处理指令或注释均会在处理过程中被去除。
XSLT 定义了四个可通过算法生成特定输出节点的指令(xsl:comment、xsl:processing-instruction、xsl:element 和 xsl:attribute)。这些指令可用于替换文本结果元素。由于它们大量使用属性值模板,因此非常适合动态输出格式。这四个指令中,xsl:comment 和 xsl:processing-instruction 最容易理解。这两个指令的语法如下:
<xsl:comment>
<!-- Content: template -->
</xsl:comment>
<xsl:processing-instruction
name= { ncname }>
<!-- Content: template -->
</xsl:processing-instruction>
请注意,xsl:processing-instruction 的名称属性是一个属性值模板,它允许动态生成处理指令的 [target]。要了解这些指令的作用,请参看以下命名的模板:
<xsl:template name='annotated' >
<!-- comment number one -->
<?Magnum PI?>
<xsl:comment>comment number two</xsl:comment>
<xsl:processing-instruction name='A'>
PI</xsl:processing-instruction>
</xsl:template>
执行时,此命名模板会发出以下内容:
<!--comment number two--><?A PI?>
如前所述,模板中出现的文字注释和处理指令不会发送到结果文档中。
xsl:attribute 指令可将属性添加到当前结果元素的 [attributes] 属性中。xsl:attribute 指令的语法如图 5所示。xsl:attribute 元素的 [children] 被解释为产生结果属性的 [children] 的文本。xsl:attribute 指令不能在 [children] 被添加到当前结果元素之后使用。
请参看以下命名模板:
<xsl:template name='simple' >
<bob><xsl:attribute name='id'>32</xsl:attribute><x/></bob>
</xsl:template>
由于 xsl:attribute 指令作为 bob 元素的 [children] 出现,则在结果文档中属性将与该元素关联。此命名模板将产生以下结果:
<bob id='32'><x/></bob>
请注意,id 属性的 [children] 是通过将 xsl:attribute 指令的 [children] 作为模板计算而生成的。
xsl:attribute 指令的能力在于其动态性。请考虑以下使用 xsl:if 和属性值模板的命名模板:
<xsl:template name='fancy' >
<xsl:param name='sAttName' select='bob' />
<xsl:param name='bUseAttribute' select='true()' />
<xsl:param name='sAttValue' />
<bob>
<xsl:if test='$bUseAttribute' >
<xsl:attribute name='{$sAttName}'>
<xsl:value-of select='$sAttValue' />
</xsl:attribute>
</xsl:if><x/>
</bob>
</xsl:template>
若按以下方式调用此命名模板
<xsl:call-template name='fancy' >
<xsl:with-param name='sAttName' >myAttr</xsl:with-param>
<xsl:with-param name='bUseAttribute' select='true()' />
<xsl:with-param name='sAttValue'>Hi, XSLT</xsl:with-param>
</xsl:call-template>
将生成以下结果代码:
<bob myAttr='Hi, XSLT'><x/></bob>
如此处所示,xsl:attribute 提供了大量的灵活性。
默认情况下,xsl:attribute 会发出一个与任何命名空间均不相关的属性。此行为可通过以下两种方式之一进行改变。将 xsl:attribute 指令与命名空间相关联的最简单方法是使用命名空间属性:
此模板将产生以下结果:
<bob xmlns:auto-ns1='http://b.com' auto-ns1:id='32/>
请注意,由于只提供了 [local name] 属性,XSLT 处理器需要创建命名空间声明和前缀。使用的前缀可通过指定 QName(而不是NCName)来控制。
<xsl:template name='ns1' >
<bob>
<xsl:attribute namespace='http://b.com'
name='b:id' >32</xsl:attribute>
</bob>
</xsl:template>
它将产生以下结果:
<bob xmlns:b='http://b.com' b:id='32/>
将 xsl:attribute 指令与命名空间相关联的一个可选方案是使用不包含相应命名空间属性的 QName。这会让 XSLT 处理器参照 [in-scope namespace declarations] 属性来派生命名空间 URI。
<xsl:template name='ns1' xmlns:b='http://b.com'>
<bob>
<xsl:attribute name='b:id' >32</xsl:attribute>
</bob>
</xsl:template>
此模板产生的结果与上一示例中的结果完全相同。
该指令族的第四条指令是 xsl:element。它是文本结果元素的替换品。xsl:element 指令和文本结果元素都会在结果文档中出现元素。而只有 xsl:element 指令能够使用 XPath 表达式动态生成元素名称。xsl:element 指令的语法如图 5 所示。
xsl:element 元素和文本结果元素具有相同的基本使用模型。例如以下使用文本结果元素的命名模板
<xsl:template name='elems' >
<bob xmlns='http://example.com'><steve/></bob>
</xsl:template>
可改写为:
<xsl:template name='elems' >
<xsl:element name='bob' namespace='http://example.com'>
<xsl:element name='steve' namespace='http://example.com'/>
</xsl:element>
</xsl:template>
或使用以下混合样式:
<xsl:template name='elems' >
<xsl:element name='bob' namespace='http://example.com'>
<steve xmlns='http://example.com'/>
</xsl:element>
</xsl:template>
xsl:element 使用的命名空间前缀的控制方法与 xsl:attribute 相同。
如前所述,有多种方法可以添加结果元素的 [attributes] 属性。.最常用的方法是使用 xsl:attribute 指令。比较不常用的方法是使用属性集。属性集是作为 xsl:stylesheet 元素的[children] 出现的 xsl:attribute 指令的集合。属性集由 QName 命名,并由 xsl:element 指令或文本结果元素的 use-attribute-sets 属性引用。
请参看图 6 中的样式表。图 6给定此样式表,elems2 模板将产生以下结果:
<bob a='a-val' b='b-val' c='c-val' ><steve c='c-val'/></bob>
允许一个属性集在定义中使用 use-attribute-sets 属性包含其他属性集是合法的。
多重样式表
XSLT 提供了两种将 xsl:stylesheet 分成多个文档的机制。最简单的机制是 xsl:include 指令,它通过 URI 引用外部 xsl:stylesheet。当 xsl:include 指令作为 xsl:stylesheet 的子项出现时,所引用文档中的 XSLT 指令将被插入当前文档中,就好像被内联定义一样。例如,请参看图 7 中所示的两个 XSLT 样式表。图 7第二个样式表使用 xsl:include 指令来包含第一个样式表中的指令。结果样式表如下:
<?xml version='1.0' ?>
<!-- stylesheetb.xsl -->
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template name='func-a' ><a/></xsl:template>
<xsl:template name='func-b' >
<xsl:call-template name='func-a' />
</xsl:template>
</xsl:stylesheet>
多次重复定义相同的模板名称会产生错误,因此多次 xsl:include 文档是危险的。
除了大量包含外,XSLT 还通过它的 xsl:import 指令支持 collision-aware 导入机制。与 xsl:include 类似,xsl:import 会将外部样式表的内容与导入样式表的内容合并。xsl:import 与 xsl:include 之间的区别仅在命名冲突时才会出现。使用 xsl:include 时,如果外部样式表使用与包含样式表相同的标识符来定义命名的构造函数(如模板规则),将会产生错误。使用 xsl:import 时,则希望外部样式表使用与包含样式表相同的标识符来定义命名的构造函数。遇到这种冲突时,导入样式表的构造函数优先。
事实上,决定冲突定义的优先级是有层次的。如果 xsl:import 指令与导入样式表中的构造函数发生冲突,则被导入样式表中的构造函数会被导入样式表中的构造函数隐藏。如果同一样式表中的两个 xsl:import 指令产生冲突,则第一个被导入样式表中的构造函数表将被第二个被导入样式表中的构造函数隐藏。
为保持准确性,所有 xsl:import 指令均必须作为 xsl:stylesheet 元素的初始 [children] 出现。xsl:import 元素在 xsl:include、xsl:template 或其它任何顶层样式表元素之后出现都是不合法的。
图 8 显示使用聚合样式表的 xsl:import 元素的样式表集合。假定 root.xsl 样式表是初始样式表,调用 func-b 模板的结果将是:
<second/>
first.xsl 中的 func-a 定义将由 second.xsl 中的定义所隐藏。即使 second.xsl 导入 third.xsl,由于 second.xsl 已被视为导入样式表,其 func-a 的定义将隐藏 third.xsl 中的定义。
基于模式的模板
至此为止,本文中出现的模板规则已根据符号名称命名并调用。XSLT 还支持根据模式匹配来调用模板规则。XSLT 模式是可标识源文档子集的格式化 XPath 表达式。XSLT 模式包含一个或多个由 | 分隔的位置路径。包含在 XSLT 模式中的位置路径仅能使用子轴和属性轴。但是,XSLT 模式中的位置路径可以使用 // 运算符,但不可以使用等效的 descendant-or-self 轴。
模式可使用匹配属性(而不是名称属性)与模板规则相关联。
<xsl:template match='author|illustrator' >
<contributor />
</xsl:template>
此模板规则可匹配名称为 author 或 illustrator 的元素。模板规则还可以使用谓词和多位置步骤。
<xsl:template match='book//author[@name != "Don"]' >
<contributor />
</xsl:template>
该模板规则匹配如下 author 元素:其 book 元素为祖先元素,但没有 [children] 为字符串 “Don” 的名称属性。
多个模板规则匹配一个给定节点是很常见的。在这些方案中,实际使用的模板规则由以下规则决定:
• | 1. 只有其匹配模式能够匹配当前节点的模板规则才会被选取。 | ||||||||||||
• | 2. 在匹配模板规则中,会先选择具有较高导入优先级的模板规则。 | ||||||||||||
• | 3. 在具有相同导入优先级的匹配模板规则中,会先选择具有较高优先级的模板规则。优先级计算方法如下:
| ||||||||||||
• | 4. 具有相同优先级和导入优先级的多个匹配模板规则会产生错误。如果发生此类错误,XSLT 处理器会报告错误,或在封闭样式表中选择上一个模板规则来解决这个问题。 |
这些规则已在 XSLT 规范的 5.5 小节中详细说明(请参阅 http://www.w3.org/TR/xslt)。
通常,模式匹配规则会从导入优先级最高的样式表中选择最合适的匹配。请参看以下样式表片段:
<xsl:transform version='1.0' xmlns:a='http://awl.com'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template
match='/a:product/a:writer'><a/></xsl:template>
<xsl:template match='a:writer' ><b/></xsl:template>
<xsl:template match='a:*' ><c/></xsl:template>
<xsl:template match='*' ><d/></xsl:template>
</xsl:transform>
使用图 1 中的 XML 源文档,与 author 元素对应的元素节点将匹配其中任何一个模板规则。由于匹配不止一个,因此要考虑优先级。根据上述规则,这些模板规则的默认优先级依次是 0.5、0、-0.25 和 -0.5。由于只有一个最高优先级模板规则(即第一个规则),系统将选择此规则来执行。相反,book 元素仅匹配第三个和第四个模板规则,但由于第三个规则具有更高的优先级,所以选中第三个规则。
请注意,这些计算假定,所有模板规则均出现在同一样式表中,并且导入样式表均没有匹配模板规则。(在匹配规则中,导入优先级是选择模板规则时最重要的因素。)另外还应注意,显式优先级属性会在很大程度上影响优先级次序。
基于模式的模板规则无法由 xsl:call-template 指令调用,而是由 xsl:apply-templates 指令调用。
<xsl:apply-templates
select=node-set-expresssion : node()
mode=qname>
<!-- Content: (xsl:sort | xsl:with-param)* -->
</xsl:apply-templates>
xsl:apply-templates 指令与 xsl:for-each 指令非常相似。这两条指令都有一个节点集,并按某种次序将模板迭代应用到每个节点上。对于 xsl:for-each 指令,应用的模板只是该指令的 [children]。对于 xsl:apply-templates 指令,模板是根据对所有已知模板规则的模式匹配进行选择的。同样,与 xsl:for-each 指令一样,通过 xsl:apply-templates 调用模板规则会改变被计算模板的上下文环境,该计算是根据 select 表达式返回的节点集和迭代序列中的当前节点执行的。
请参看以下使用模式匹配和 xsl:apply-templates 来调用模板规则的 XSLT 样式表:
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match='author' >
<by><xsl:value-of select='@name' /></by>
</xsl:template>
<xsl:template match='book' >
<doc><xsl:apply-templates /></doc>
</xsl:template>
<xsl:template match='/' >
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
假定使用图 1 中的源文档,此样式表会产生以下结果文档:
<?xml version='1.0' ?>
<doc><by>Don</by><by>Aaron</by><by>John</by></doc>
下面让我们来看一下该样式表的处理过程。XSLT 处理始终从最匹配源文档根节点的模板规则开始执行。该节点由 XSLT 模式 / 匹配,通常用于查找初始模板规则。若不存在显式模板规则,本样式表中还有一个内容完全匹配第三个模板规则的内置模板规则。(稍后,本节还会进一步讨论内置模板规则。)在本样式表中,第三个模板规则将被选为初始模板规则。该模板规则只调用了 xsl:apply-templates 没有 select 属性的指令,在默认情况下相当于:
<xsl:apply-templates select='node()' />
使用相同的源文档,该 select 表达式将返回包含一个元素节点 (book) 的节点集。由于只有一个匹配名为 book 的元素节点的模板规则(第二个规则),系统将使用 book 元素节点作为当前上下文节点调用此规则。
计算第二个模板规则之后,将发出文本结果元素 doc。但是,在该元素中还有另一个 xsl:apply-templates 指令。该指令的隐式 select 表达式将返回一个包含三个 author 元素节点的节点集。 xsl:apply-templates 指令只寻找一个匹配模板规则,即第一个规则。在计算最后一个模板规则后,文本结果元素需要把遇到的 xsl:value-of 作为其 [children]。由于 xsl:apply-templates 通过调用模板规则来改变上下文,xsl:value-of 中使用的相关 XPath 表达式将根据当前 author 节点进行计算。
XSLT 定义了一个包含七个内置模板规则(每个节点类型一个)的集合,该集合在模板规则匹配过程中是隐式考虑的。为这些内置规则赋予最低的导入优先级,表示只有在所有可用样式表中都没有明确定义的可用模板规则时,才会选择这些规则。元素和根节点的内置规则会对所有的子节点递归应用最佳匹配模板。
<xsl:template match='*|/' >
<xsl:apply-templates select='node()' />
</xsl:template>
文本和属性节点的内置模板规则只会通过以下方式复制文本值:
<xsl:template match='text()|@*' >
<xsl:value-of select='.' />
</xsl:template>
注释和处理指令节点的内置模板规则会通过进一步地处理来丢弃节点:
<xsl:template match='processing-instruction()|comment()' />
命名空间节点的内置模板规则也会通过进一步地处理来丢弃节点,但是由于 XSLT 模式不支持匹配命名空间节点,此内置模板规则无法被重写。
讨论内置模板规则,就必须提到 xsl:copy 指令。尽管 xsl:copy 和 xsl:copy-of 指令具有相似的名称,但它们还是有很大区别的。xsl:copy 指令可发出独立于类型的当前上下文节点的副本。如果节点类型是元素节点,则与之关联的命名空间节点也会被一起复制,但是元素的 [attributes] 和 [children] 不会被复制。要复制这些节点,您必须包含 xsl:apply-templates 指令来递归地复制下级节点。以下是执行标识转换的样式表:
<xsl:transform version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match='@*|node()' >
<xsl:copy>
<xsl:apply-templates select='@*|node()'/>
</xsl:copy>
</xsl:template>
</xsl:transform>
请注意,这个独立的模板规则匹配所有属性节点和所有子节点,包括注释、处理指令和文本节点。模板本身只使用 xsl:copy 指令来传递当前节点。xsl:copy 指令内的子模板将递归地调用所有子节点和属性节点的模板规则。
控制输出
默认情况下,XSLT 样式表产生 XML 文档。这可以使用 xsl:output 指令来改变。xsl:output 指令必须作为 xsl:stylesheet 元素的子元素出现,且必须遵循以下语法:
<xsl:output
method = "xml" | "html" | "text" | qname-but-not-ncname
version = nmtoken
encoding = string
omit-xml-declaration = "yes" | "no"
standalone = "yes" | "no"
doctype-public = string
doctype-system = string
cdata-section-elements = qnames
indent = "yes" | "no"
media-type = string
/>
这些属性中最重要的是方法属性。方法属性设置 XSLT 样式表的输出方法。如果未提供,默认为典型的 XML。(若元素 xsl:output 不存在,则会使用一组试探法来检测 Web 文档作为结果文档。有关这些试探法的详细信息,请参阅 XSLT 规范。}
XSLT 还提供了两种其他的输出方法:文本和 HTML。前者假定结果文档仅是文本文件,没有任何隐式结构。后者假定结果文档是 Web 文档,其版本由版本属性控制。请参看以下 XSLT 样式表:
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform' >
<xsl:template match='/' >
Hello, World
</xsl:template>
</xsl:stylesheet>
可见,该 XSLT 样式表是非法的,结果文档并不是正规的 XML。要使得该样式表合法,需要添加一个 xsl:output 指令:
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform' >
<xsl:output method='text' />
<xsl:template match='/' >
Hello, World
</xsl:template>
</xsl:stylesheet>
给定该示例样式表,将产生以下结果文档:
Hello, World
由于这不是 XML 文档,因此没有 XML 声明,也没有任何良好的期望结果。
输出方法还控制了五个内置实体(lt、gt、amp、apos 和 quot)的处理方式。 在 method='text' 的模式下,对这五个内置实体的所有引用都会在转换为结果文档之前转换为其文本值。在所有其他模式下,默认行为始终会发出对这五个字符的实体引用。使用 disable-output-escaping 属性可以停用此行为。该属性可出现在 xsl:value-of 指令和 xsl:text 指令中。
xsl:text 指令用于根据 xsl:text 指令的字符数据 [children] 来发出字符数据。请参看以下命名模板:
<xsl:template name='emitme' >
Hel<lo, <xsl:text>Wo>rld</xsl:text>
</xsl:template>
在 method='text' 模式下,此命名模板的结果将为:
Hel<lo, wo>rld
在 method='xml' 模式下,此命名模板的结果将为:
Hel<lo, wo>rld
使用 disable-output-escaping 属性可以防止出现第二个引用实体。请考虑对上一个命名模板的细微改动:
<xsl:template name='emitme' >
Hel<lo, <xsl:text disable-output-escaping='yes'
>Wo>rld</xsl:text>
</xsl:template>
在 method='xml' 模式下,此命名模板的结果将为:
Hel<lo, wo>rld
请注意,在元素内容中,> 字符是完全合法的。
也可以在结果文档中强制使用 CDATA 部分。xsl:output 指令的 cdata-section-elements 属性可包含一列基于 Qname 的元素名称。遇到文本节点时,若其父元素的扩展名在 cdata-section-elements 列表中,文本节点将从 CDATA 部分内部发出。
请参看以下样式表:
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform' >
<xsl:output method='xml' cdata-section-elements='bob'/>
<xsl:template match='/' >
<steve>
<bob><Hello></bob>
<george><Hello></george>
</steve>
</xsl:template>
</xsl:stylesheet>
该样式表将产生以下结果文档:
<steve>
<bob><![CDATA[<hello>]]></bob>
<george><Hello></george>
</steve>
请注意,发出 CDATA 部分后,所有内置实体引用都会展开。
到目前为止,讨论尚未涉及到空白处理的难题。XSLT 提供了一组定义良好的规则和机制,用于控制结果文档中的空白。一般而言,XSLT 会将混合内容视为特殊情况,并从源文档树和样式表中去掉所有仅包含空白的文本节点。可以通过已生效的任何 xml:space='preserve' 属性来抑制此行为。此外,样式表可以包含一个或多个 xsl:strip-space 或 xsl:preserve-space 指令,这些指令可列出要求重写行为的元素名称。
请参看以下源文档:
<?xml version='1.0' ?>
<root>
<a>
<e/>
</a>
<b xml:space='preserve'>
<e/>
</b>
<c xml:space='default'>
<e/>
</c>
<d>
<e/>
</d>
</root>
如果 xsl:strip-space 或 xsl:preserve-space 指令未生效,则该源文档与以下处理过的源文档完全相同:
<?xml version='1.0' ?>
<root><a><e/></a><b xml:space='preserve'>
<e/>
</b><c xml:space='default'><e/></c><d><e/></d></root>
请注意,只保留了 b 元素的空白。假定样式表文档中将出现以下两个指令:
<xsl:strip-space elements='b' />
<xsl:preserve-space elements='a root'/>
源文档应预先执行去除处理,以便与以下文档完全相同:
<?xml version='1.0' ?>
<root>
<a>
<e/>
</a>
<b xml:space='preserve'><e/></b>
<c xml:space='default'><e/></c>
<d><e/></d>
</root>
请注意,xsl:strip-space 指令能够重写 xml:space 属性的值。上述去除行为可应用于样式表和原文档。主要区别在于,xsl:text 元素可自动保存在空白保持列表中,且不需要明确地在 xsl:preserve-space 指令中列出。
小结
XSL 转换解决了描述补充数据的多重 XML 架构增长导致的主要问题。通过 XSLT,您可以使用喜欢的编程语言将 XML 文档彼此映射,并创建任意文本格式的输出(包括 XML)。当然,只有映射文档无法确保它们互操作的准确性(解释数据仍需要人员的干涉),但 XSL 转换迈出了简化任务的宝贵的第一步。