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 和 xsl-value 指令
包含源文档内容的最简单方法是使用 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'> <