使用XSLT转换XML
SLT1.0 是W3C标准,主要用于对XML文档的转换,包括将XML转换成HMTL,TEXT或者另外格式的XML文件.XSLT1.0可以与XPATH1.0标准一起使用,XPATH会告诉你要转换的节点而XSLT则提供了一种机制来说明如何实现这种转换。为了将源文档转换成想要的格式, 一个XSLT样式文件往往包含一系列的规则,而要解释这些规则, 需要依赖XSLT处理器,这个处理器实际上就是对XSLT1.0标准的实现,所以大家可以按照这个标准提供自己的实现,处理器可以根据标准元宝的所有特性来来执行规则,最终完成转换的工作。
XSLT的处理机制
XSLT样式表用于定义转换逻辑,这些逻辑会应用于源XML文档的树状形式的节点集上,然后生成树状结构的节点集做为输出结果。
下面的XML文档比较简单,我们就以此做为源文件:
<!-- Emp.xml --> |
每个XML文档必须有一个代表该文档的根结点, 并且这个根结点的子节点可以包含文档节点(具有唯一性,在上面的例子中就是<ROWSET>,大家要清楚根节点跟文档节点的区别),注释节点和指令结点。文档节点可以包含任意数量的文本节点和元素节点,同时,这些子节点仍可以包含其他任意数量的子节点,这些节点以相互嵌套的方式形成了一棵树。
所以,要实现文档转换, 必须要有两个组成部分:
1 源XML文档, 在内存中,它是以树状的节点集形式表现,了解DOM的人应该容易理解。
2 XSLT文档,其中包含一系列的转换规则。
XSLT本身也是XML格式的文档,不同的是XSLT文档支持相应的指令标签,以实现转换的功能, XSLT的文档节点是<xsl:stylesheet>,该节点下面包含所有的转换规则,每个规则一般都与XPATH关联,XPATH表明,这个规则适用于哪个节点,当XSLT处理器在解释源文档的某个节点的时候,会查找匹配这个节点的规则。当然,这种规则也被称做Template, 在XSLT中是用标签<xsl:template>表示,该标签有个match属性来关联XPATH表达式。比如,像下面的这个规则,它应用于源文档的根节点,”/” 是XPATH表达式,说明这个规则适用于根节点。
<xsl:template match="/"> |
类似,像下面的模板:
<xsl:template match="ROWSET/ROW[ENAME]"> <!-- 输出的内容:文本,属性 等等. --> |
就仅作用于源文档中的<ROW>节点集,一个<ROW>节点下面还含有<ENAME>子节点,并且这个<ROW>必须是<ROWSET>节点的直接子节点,才能应用这个规则。
为什么要用TEMPATE来表示一个规则呢?因为在应用这个规则的时候,包含在规则里面的文本和元素就像是个模板,每次XSLT处理程序在调用同个规则的时候,输出的结果都是以这个模板为基础的,模板保证了输出的结构是一致。
让我们看下,以下的规则会输出什么:
<xsl:template match="ROWSET/ROW[ENAME]"> |
XSLT处理程序在解释<ROW> 节点时,会查找匹配的规则,这个模板的match属性值是“ROWSET/ROW[ENAME]”,刚好符合要求, 最后程序会调用这个模板,输出结果。
当匹配的模板被实例化后,接下来还会做三件事情:
1) 模板中的文本和属性直接输出。任何不是以XSL命名空间开头的都被认为是文本,像上面例子里的<Employee> 和 id 属性,它们会以文本形式直接输出。
2) 以大括号表示的元素,像{XPathExpr}, XSLT会计算它的值并将值以文本形式返回到当前的位置,我们可以理解为是一个表达式点位符。
3) XSLT命名空间下的任何元素,都以文档中的先后顺序被执行。比如执行<xsl:value-of>元素,处理程序会根据select属性中的XPATH表达式获取到值,并以文本节点的形式替换<xsl:value-of>元素。
基本的逻辑可以归纳为,当源文档中的节点与XSLT中的某个规则匹配的时候,规则中的内容就会输出到结果树中。一旦你掌握了这个过程, 那整个XSLT处理模型就容易理解了。 给你一个源树(XML文档的树状表示)和XSLT样式表,XSLT处理程序
依照样式表中的规则说明的那样,一步步的执行这些规则,最终将源文档转化成结果树。
在执行源树节点集的过程中, 会产生结果树的一部分或称做“片段”,当然,到最后这些片段会被整合在一起。当前正在被执行的节点,称做当前节点, XSLT处理器会选择所有适用于当前节点的模板,如果适用的模板有多个,处理器会根据内置的规则(这个后面再细说),从中选取一个最匹配的,因为针对一个节点只能使用一个模板。
执行的时候,XSLT处理器从根节点开始,它搜索匹配根节点的模板,一般来说, 匹配根节点的模板,它的match属性等于"/",找到,处理器实例化该模板,并将内容输出到结果树, 通常都要执行这三个步骤来完成工作。
如果模板还包含需要处理其他节点的XSLT指令, 那么处理器会重复上面的步骤,搜索模板,应用模板,输出结果,这是个循环的过程,直到所有的XSLT指令都被执行完成。执行完成后,转化过程产生的结果树,就是我们想要的目标文档。
单模板实现
理论上说,只要定义一个模板就可以实现转化的过程,接下来,我们会创建这样的一个模板来进行说明,当然,在一个XSLT样式表中定义多个模板,会给我们带来更多好处,这个在后面详细介绍。
像下面的样式表,主要作用是将XML文档转换成HTML格式的文本。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
或者另外一种更简洁的表示:
<!—这种方式,隐藏了匹配根节点的模板 --> <!-- |
当看到XSL命名空间的申明语句:xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ,你很自然的就会想到,当执行这个样式表的时候,XSLT处理器会访问这个URL地址。然而,这个申明的作用仅仅是做为唯一的字符串来标记XSLT的命名空间, 命名空间的作用是为了区别各自的标签,因为XML是允许自定义标签的,这会导致出现相同的标签名的可能性大大增加,为了避免冲突,突显自定义标签的唯一性,引入了命名空间的概念。XSLT用XSL作用命名空间的别名, <xsl:template>, <xsl:for-each>, <xsl:value-of>这些标签就表明他们是XSLT相关的标签,XSLT处理器会根据XSL前辍来识别它们,如果你移除了XSL前辍,XSLT处理器就不可能识别出它们。
考虑一下以下的样式表,它只包含一个模板,这个例子的目的就是将EMP.XML转化成HTML。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
输出结果:
![]() |
上面的模板混合了HTML的标签<html>, <body>, <table>, <tr>, and <td>,和 <xsl:for-each> and <xsl:value-of>,当XSLT处理器实例化这个模板的时候,根节点就是当前节点,
<xsl:for-each>标签 :
1) 选择源XML树中所有的"ROWSET"节点集。
2) 将选中的节点集做为当前正在处理的节点集。
3) 开始执行这些节点集。
针对节点集中的每个节点,<xsl:for-each>里的内容都被实例化到结果树中,如果这个实例化后的片段,含有其他的XSLT元素,需要解释执行,碰到<xsl:value-of>元素的话,会用XPATH表达式计算后的结果替换当前位置。
生成的HTML:
<html> |
在这个例子中, XSLT处理器仅仅执行与根节点匹配的模板, 所有的子节点处理都依靠执行<xsl:for-each>来完成。
如果模式表只用一个模板,那么我们可以用更简洁的方式来实现,像<xsl:stylesheet> 和<xsl:template match="/">都可以不需要。这种情况下, 文字元素在模板中是做为第一个元素。但是你必须包含XSLT的命名空间,并且要加上xsl:version="1.0"属性。
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
与前一个相比,写法稍有不同,但它们输出的结果是完全一样的,而且后一种看起来更简洁明了。
理解输入和输出选项
在之前的XSLT转化过程,我们都有谈到节点树这个概念,节点的树状结构其实并不存在,它只是为了便于我们理解的一种逻辑形式,从XSLT处理器的角度来说,它就是一堆连续的符号,是其在内部执行源XML文档和输出转换结果的过程中的逻辑表现形式,实际情况是:
1) 源文档是以可读的文本形式存在。
2) 转化的结果需要以其他可读的方式输出,比如,以文本形式保存到文件中或以流的方式输出到浏览器。
转换的输入信息必须是节点树,这可以通过解析源XML文档或者手动编程的方式构造出一个树(通过DOM 或SAX提供的API)。
所有的XSLT转换都是通过解析源节点树,生成结果节点树,当你的应用中有多个顺序执行的转化过程,那么一个转化过程产生的节点树会做了下个转化过程的输入,直到所有的转化过程都结束为止,所有的节点集做为一个整体以字符流的方式输出。这个过程称做序列化结果树。
根据XSLT 1.0规范的描述, 利用默认的序列化规则,在通用情况下可以使我们的XSLT文件看起来更简洁。XSLT1.0用UTF-8做为转化输出结果默认的编码集 同时还支持以下几种输出格式:
- 支持缩进的,格式规范的HTML , 它的类型是text/html
- 无缩进的XML, 没有DOCTYPE属性,它的类型是text/xml
抛开这些默认的选项, 标准的XSLT语法需要以<xsl:stylesheet>开头,然后包含<xsl:output>子元素,通过这个子元素来控制输出序列化过程。
要想明白如何控制序列化,最主要的就是理解output元素中的method属性,这个属性决定XSLT处理器如何将结果集序列化到输出流。XSLT 1.0支持三种不同的输出选项:
- <xsl:output method="xml"/>, 这个是默认项,输出XML格式。
- <xsl:output method="html"/>, 如果结果树的文档结点的<html>,这个就是默认项。要注意的是,它的输出并不是格式良好的XML。
- <xsl:output method="text"/>, 这种方式只会将结果树中的文本结点顺序输出。一般应用于,将XML转化成编程相关的源代码,邮件内容或其它的文本输出。
考虑下面文档中的例子:
<!-- King.xml --> |
用下面的XSLT样式表来转化King.xml,将 <ROWSET>节点转化成 <Invitation>:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
输出结果:
<?xml version="1.0"?> |
记住,XSLT样式表是格式良好的文档,所以有些特殊字符需要转义,像“&”转义成“&”和“<”转成“<”
还要注意的就是像“&”这样的数值型实体字符,它是数字“38”的Unicode编码形式。如果你习惯这种十六进制的表示,像换行,你可以用
来代替。
指定output为html, 下面的样式表会将<ROWSET>转化成简单的,包含图片的HTML页面。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
用这个样式表来转化King.xml,输出的结果:
<html> |
如果指定output为text将<ROWSET>转化成文本,那么输出的结果不包含任何标签:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
结果:
Hello King & Family, |
注意, 我们转化的样式表中用<xsl:text>来处理文本内容,一般来说, 空白字符在样式表中是被忽略的, 所以可以实现标签的缩进,以达到更好的可读性。 但是,文本内容中的空格需要被保留,<xsl:text>可以帮助我们实现这个目的,这样,空白符就会出现在输出的文本中。除了空白字符外,还有换行符和TAB(制表符),利用<xsl:text>元素,这些符号都会被逐字保留。上面例子中的“
”代表回车换行符。
下图说明了源文档,源节点树,结果节点树和利用这三部分实现的序列化以及通过指定output,输出不同的格式。
除了outpu属性, 还有其它几个属性可以用来控件输出行为。
![]() |
多模板实现灵活性
我们都清楚, 样式表包含一组规则, 当我们用单个模板方式的时候,整个样式表就只有一个规则:“匹配源文件的根节点,执行其中所有的XSLT指令。”这种方式,就像我们在Java编码的时候,将所有的逻都放在一个main()函数里,肯定会有人赞成,有人反对。
public class doit { |
做为开发人员,刚入门的时候会觉得将所有实现都放在单个方法里比较容易理解,但他们很快就会发现,当逻辑越来越复杂的时候,在单个方法可能有很多可以共用的部分,如果能将它们单独做为一个方法, 可以更好的提供代码的可重用性,多模板也是基本这个考虑。如果采用多模板,我们也可以利用人家已经实现的规则,就好比站在巨人的肩膀上,可以让你节约时间,而且你也可以新建一个自己规则,替换老的。
我们可以发现,XSLT编程与JAVA编程在很多方面的类似性。在JAVA里,每个方法是包含形为的整体而且可以被重写。如果你实现一个类,并将所有的代码逻辑都放在main()函数里,那么要是有人准备扩展你的代码,那就只能重写main()方法,尽管有时候,他们只是需要一个很小的改动。那最有效的方式,就是将一个方法中的逻辑拆分成几个子方法,而且这些子方法应用易于重用,易于重写。
在XSLT中, 模板是形为和重写的基本单元。 就像上面提到的JAVA一样,如果你将样式表中的逻辑分成多个可重用的模板, 那么其他人就有可能继承你的模板,然后调用你写好的模板或者重写模板以实现他们自己的行为逻辑。
根据每个转化任务来拆分模板是最有效果的。你的模板越容易被调用,越容易被人重写,就说明你的拆分越合理。
应用多模板
下面的例子中, 我们会将上面提到的单个模板进行细化,分成多个模板,细化后的每个模板都对应源文档中的一个元素,负责对应元素的转化工作。每个模板都用<xsl:apply-templates>指令告诉XSLT处理器,如果当前元素还有子元素,需要递归遍历,直到所有的节点都处理完成。
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> |
我们可以举其中的某个模板为例:
<xsl:template match="ROWSET"> |
它的含义是: 对于源文档中的<ROWSET>元素, XSLT处理器会应用这个模板来进行转化,该模板会在结果树中构建一个<table>元素,因为模板包含<xsl:apply-templates/>指令,处理器会继续查找<ROWSET>元素的所有子节点,然后用相应节点的模板来转化,直到所有的节点都转化完成,最终,各个节点的转化片段组成大的结果树,做为<table>元素的子元素。
通常情况,处理器会从源文档的根节点开始,搜索匹配的模板,在我们的样式表中就是match="/"的那个模板,“/”符号就是表示根节点。所有这个根节点就做为当前节点被实例化。根节点模板构造出<html> 和<body> ,然后调用<xsl:apply-templates>去处理根节点的所有子节点。 这些子节点包含一个注释节点和一个元素节点<ROWSET>,为了构造这些节点的结果树片段,处理器按顺序执行所有的子节点。注释节点会被忽略(这个稍后解释),对于<ROWSET>节点,会有相匹配的模板(match等于"ROWSET")来处理。
下面的图说明XSLT处理器的顺序处理的过程。
![]() |
所有的模板都会被实例化,然后输出到结果树中。根节点模板会输出<html> 和<body>元素,"ROWSET"模板输出<table>,嵌套在<body>元素里面,接下来,执行<xsl:apply-templates>指令,匹配所有的<ROWSET>子节点,<ROWSET>节点包含以下四个节点,按顺序排列:
1. 空白节点
2. <ROW> 节点
3. 空白节点
4. <ROW> 节点
针对这些节点,处理器会查找匹配的模板,实例化模板,然后通过模板转化节点,输出到结果树中。对于空白节点,默认情况下,系统会直接拷贝空白字符,而match等于"ROW"的模板会构造出两个<tr>元素,然后继续处理其他节点。
转化完成的结果跟单个模板输出的结果是完全一致的,但是,在接下来的几个例子中, 多模板方式会显现出其强大的好处。
理解内置模板
在继续之前, 我们需要解释一下,为什么注释节点会被处理器忽略? 对于”7839“, ”KING“, “7788, 和“SCOTT”这样的空白节点和文本节点处理器是如何进行转化的?
要解答这些问题,不得不提到XSLT中的内置模板,这些内置模板是XSLT处理器的默认组成部分。
<xsl:template match="/|*"> 匹配根节点或任何节点,这个内置模板不输出任何东西,但会告诉处理器去执行源文档当前节点下的所有子节点。 匹配文本节点或属性节点,将当前节点的值输出。 匹配指令节点或注释节点,但什么也不做。 |
为什么需要内置模板,设想一下,如果有人只想匹配源文档中的某个节点,但是默认情况下,XSLT处理器都是从根节点开始匹配,如果没有内置模板,系统会提示模板不存在就会报错,要是每个开发人员都要将这些模板在自己的样式表中都实现,会使样式表看起来不够简洁。
会了更好的理解内置模板,我们会用下面的样式表来转化文档”Emp.xml “,该样式表中不包含任何模板:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> |
得到的结果是:
<?xml version = '1.0' encoding = 'UTF-8'?>
|
处理器用内置的模板来匹配源文档中的元素,对于根节点元素,内置模板不会做任何的输出,只会循环遍历它的子元素,当子元素是空白元素的时候或者”7839“, ”KING“, “7788, 和“SCOTT”这样的文本元素,就会用内置的text()来匹配,调用<xsl:value-of select="."/>来拷贝当前文本节点的元素值到结果树中。 相应的, 结果树就是源文档中的一堆文本内容,来自任何层次的节点,以文档中显示的顺序输出。尽管这很有意思,但我们不会将这种不包含任何模板的样式表用在实际的项目中。
通配符匹配和空白字符的处理
让我们看下下面列出的几个模板:
<xsl:template match="EMPNO"> |
实际上,这两个模板做同样的事情,它们都用来匹配<ROW>下面的两个不同的子节点, 然后构建一个表格元素<td>,其中包含相应子节点的转化结果树。但是,我们要是在<ROW>增加新的节点,叫做<COMPANY>,<DEPTNO>,那是不是我们还要建立两个新的,类似的模板呢,XSLT为我们提供了更好的解决方案,通配符。在XPATH表达式中,我们可以用通配符来指定某个结点下面的所有子节点,像这样”ROW/*“。用这种方式,可以不再需要为每个子节点设置一个匹配模板,而只要用一个泛型模板就足够了。
<!-- Match any element child of a ROW --> |
用通用的方式实现的模板,例子如下:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
输出的结果与之前的一致,但是样式表看起来更简洁。
等等, 好像跟之前的输出比较起来,还是有不一样的地方。
这段文本并没有像预期的那样缩进,但是在单模板样式表中,输出的结果是排版良好的,所有节点都有缩进。
明白导致这个问题的原因很重要,因为这关系到XSLT如何处理源文档中的空白符,回想一下,Emp.xml文档的缩进是通过空白字符和回车符实现的。如果我们将这些都显现出来的话,应该是下面的样子。
当执行匹配<ROWSET>元素的模板的时候,XSLT处理器会构建一个<table>标签,接着循环处理<ROWSET>的所有子节点,<ROWSET>包含下面几个节点:
1. 文本节点,包含空白字符用来缩进:carriage return, space, space
2. <ROW> 节点
3. 文本节点,包含空白字符用来缩进:carriage return, space, space
4. <ROW> 节点
用多模板方式,XSLT处理器顺序执行<ROWSET>的所有子节点,查找匹配的模板。当匹配第一个空白节点的时候,因为没有明确的模板,处理器会调用内置模板"text()|@*"来处理这个节点,这个模板会将空白字符直接拷贝到结果树,对于模板来说,空白节点跟文本节点是一样的,同时,回车符也被直接输出到结果树中,就这是导致缩进不一致的问题。
那么单模板方式为什么没有这个问题? 单模板在匹配根节点后, 通过执行<xsl:for-each>指令来选择节点集,这些节点集中不包含空白节点,所以不存在上面提到的困扰。
要解决这个问题, 我们需要告诉XSLT处理器在转化的时候,剔除这些节点,要实现这个功能, 要用到<xsl:strip-space>指令,这个指令必须放在样式表的顶部。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
与之相反,如果你要保留某个元素的空白节点,需要用<xsl:preservespace>,它同样包含有elements属性。默认情况下,XSLT处理器保留所有元素的空白节点。
不同模式下的节点处理
前面例子中,输出的结果只有数据信息,并没有包含表头,
为了创建一个通用的方式来生成表头,我们必须遍历所有的<ROW>元素,然后取出它子节点的名称做为表格的头单元。然而,我们已经有了一个匹配“ROW/*”的模板来处理<ROW>元素的子节点,现在为了创建表头,也需要处理这些节点,如果不同的模板有相同的MATCH属性,XSLT处理会根据优先规则只采用其中的一个,那么如何来区分这两种不同的应用呢,XSLT为模板提供了另外一个属性MODE:
- 假设我们指定了MODE属性值为"ColumnHeaders",那么它就与原来没有MODE属性的模板区分开来,它们虽然是处理相同的节点,但逻辑上完全不同。
<xsl:template match="ROW/*" mode="ColumnHeaders"> |
- 在用<xsl:apply-templates />调用模板时,必须指定MODE属性,像<xsl:apply-templates mode="ColumnHeaders"/>
<xsl:template match="ROWSET"> |
但是这样的写法有问题,生成表头的模板会匹配<ROWSET>下面的所有<ROW> 节点,根据<ROW> 节点的个数,会生成重复的表头信息,实际上, 我们只要处理一个<ROW> 节点就够了。
解决这个问题非常简单,只要保证我们的XPATH表达式只选择一个<ROW> 节点就行,修改<xsl:apply-templates mode="ColumnHeaders"/>为<xsl:apply-templates select="ROW[1]/*" mode="ColumnHeaders"/>。
重用和定制已有的模板
我们曾经提到过,利用多模板方式,可以重复利用已有的模板规则,甚至可以用新的模板替换老的模板,比如我们要生成一张类似上面的表格,不同的是,对于工资大于2000的行,要对其进行高亮显示,那我们要如何实现呢?
假设上面提到过的一些模板都已经存放在了样式表文件TableBaseWithCSS.xsl中,然后我们重新建了新的样式表EmpOver2000.xsl,这个文件包含新的模板,并且用<xsl:import>指定将TableBaseWithCSS.xsl引入到新的样式表中,大家都知道,TableBaseWithCSS.xsl中已经定义了转化表头和行的基本模板。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
当用EmpOver2000.xsl这个样式表来转化源文档的时候,XSLT处理器会查找<ROWSET>节点下的所有<ROW>,之前,就只有一个模板匹配这个<ROW>节点,但在新的样式表中,我们创建了match值为ROW[SAL>2000]"的模板,这意味着对于当前节点集中的<ROW>节点,如果<SAL>这个值大于2000,处理器就会发现有两个模板匹配这个节点,我们说过,处理器只会选择一个最合适的模板来进行匹配,在这里ROW[SAL>2000]的范围更具体,所以更适合。
让我们再举几个例子:
- 格式化奇数行
- 格式化偶数行
- 将DEPTNO 等于20的行,输出“Classified”
下面是样式表,包含要用到的所有模板:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> 匹配所有的奇数行--> |
position()函数用于取得当前的结点位置,mod 是取余操作符。
经过实验,ROW[DEPTNO=20]模板从来没用被调用过,这就说明,如果模板的优先级相同的话,处理器会永远选择最新的模板,比如当前样式表中的模板优于被引用样式表中的,文件中位置靠后的模板优先前面的。
使用Priorities来解决模板冲突
XSLT处理器在选择合适的模板时,遵守下面的原则:
- 通配符“*”低于指定某个节点,像ROWSET
- 某个节点低于其中带有查询条件的节点,像ROWSET[predicate]或者ROWSET/ROW
根据这种具体程序来区别多模板,万一,有多个模板,它们的程度都是一样的,处理器又该如何选择呢?一种方式就是利用priority属性,priority="realnumber"可以是任意的正负值,当处理器无法根据规则选出最恰当的模板时,模板拥有较高priority就会被选中,priority大于0.5会使你自定义的模板比内置的要优先使用。
所以,当我们将priority="2"加到模板ROW[DEPTNO=20]中,比起匹配奇,偶行的模板,这个模板就有更高的优先级,在处理DEPTNO等于20的那行时,模板ROW[DEPTNO=20]会优先被处理器使用。
定义命名模板
接下来,我们看个格式化数字的例子,下面的样式表有个format-number()函数,它的作用就是将数值转换成指定的格式。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
这里,我们还是要引用TableBaseWithCSS.xsl,因为要用到它里面的模板,当前的样式表重写了匹配节点”ROW/SAL“的模板,而且用了另外的方式来处理变化的行,原来的方式是定义两个模板来处理奇行和偶行,现在只需要一个模板就完成这个功能:
<tr class="tr{position() mod 2}"><xsl:apply-templates/></tr> |
这个模板会构造<tr>元素到结果树,同时里面包含一个class属性,根据当前行是奇数或偶数,它的值在tr0和 tr1之间变化。CSS文件的定义如下:
body { font-family: Verdana; font-size: 8pt } |
如果,你需要经常对数值进行格式化,像是对货币格式的转换,最好是再建一个模板,以方便重用。我们可以用name属性替换<xsl:template>的match属性。
<xsl:template name="moneyCell"> |
然后,无论什么时候我们想调用模板,只要执行带name属性的<xsl:calltemplate>指令。
<xsl:call-template name="moneyCell"/> |
命名模板从来不会自动被XSLT处理器执行,除非在样式表中明确的调用。当用<xsl:call-template>调用命名模板的时候,命名模板里面的字面元素和XSL指令会被实例化,就像它们就位于调用它的模板的当前位置。
与<xsl:apply-templates select="pattern"/>不同的是,<xsl:call-template>不会改变当前正在处理的结点,被调用的模板跟调用它的使用相同的节点,而xsl:apply-templates 会根据select属性的值改变节点位置,理解这一点非常重要。
像其他模板一样,命名模板可以被放在其他的文件里,相当于一个“方法库”文件,从而被其它样式表所引用。
常见错误当你在样式表中混搭使用基于match的模板跟命名模板的时候,会经常不经意出现错误:
|