使用XSLT转换XML

SLT1.0 是W3C标准,主要用于对XML文档的转换,包括将XML转换成HMTL,TEXT或者另外格式的XML文件.XSLT1.0能够与XPATH1.0标准一块儿使用,XPATH会告诉你要转换的节点而XSLT则提供了一种机制来讲明如何实现这种转换。为了将源文档转换成想要的格式, 一个XSLT样式文件每每包含一系列的规则,而要解释这些规则, 须要依赖XSLT处理器,这个处理器实际上就是对XSLT1.0标准的实现,因此你们能够按照这个标准提供本身的实现,处理器能够根据标准元宝的全部特性来来执行规则,最终完成转换的工做。html

XSLT的处理机制

XSLT样式表用于定义转换逻辑,这些逻辑会应用于源XML文档的树状形式的节点集上,而后生成树状结构的节点集作为输出结果。node

下面的XML文档比较简单,咱们就以此作为源文件:编程

<!-- Emp.xml -->
<ROWSET>
    <ROW num="1">
        <EMPNO>7839</EMPNO>
        <ENAME>KING</ENAME>
    </ROW>
    <ROW num="2">
        <EMPNO>7788</EMPNO>
        <ENAME>SCOTT</ENAME>
    </ROW>
</ROWSET>浏览器

每一个XML文档必须有一个表明该文档的根结点, 而且这个根结点的子节点能够包含文档节点(具备惟一性,在上面的例子中就是<ROWSET>,你们要清楚根节点跟文档节点的区别),注释节点和指令结点。文档节点能够包含任意数量的文本节点和元素节点,同时,这些子节点仍能够包含其余任意数量的子节点,这些节点以相互嵌套的方式造成了一棵树。app

因此,要实现文档转换, 必需要有两个组成部分:函数

1 源XML文档, 在内存中,它是以树状的节点集形式表现,了解DOM的人应该容易理解。编码

2 XSLT文档,其中包含一系列的转换规则。url

XSLT自己也是XML格式的文档,不一样的是XSLT文档支持相应的指令标签,以实现转换的功能, XSLT的文档节点是<xsl:stylesheet>,该节点下面包含全部的转换规则,每一个规则通常都与XPATH关联,XPATH代表,这个规则适用于哪一个节点,当XSLT处理器在解释源文档的某个节点的时候,会查找匹配这个节点的规则。固然,这种规则也被称作Template, 在XSLT中是用标签<xsl:template>表示,该标签有个match属性来关联XPATH表达式。好比,像下面的这个规则,它应用于源文档的根节点,”/” 是XPATH表达式,说明这个规则适用于根节点。spa

<xsl:template match="/">
     <!-- 输出的内容:文本,属性 等等. -->
</xsl:template>.net

相似,像下面的模板:

<xsl:template match="ROWSET/ROW[ENAME]">

<!-- 输出的内容:文本,属性 等等. -->
</xsl:template>

就仅做用于源文档中的<ROW>节点集,一个<ROW>节点下面还含有<ENAME>子节点,而且这个<ROW>必须是<ROWSET>节点的直接子节点,才能应用这个规则。

为何要用TEMPATE来表示一个规则呢?由于在应用这个规则的时候,包含在规则里面的文本和元素就像是个模板,每次XSLT处理程序在调用同个规则的时候,输出的结果都是以这个模板为基础的,模板保证了输出的结构是一致。

让咱们看下,如下的规则会输出什么:

<xsl:template match="ROWSET/ROW[ENAME]">
     <Employee id="NX-{EMPNO}">
           <xsl:value-of select="ENAME"/>
     </Employee>
</xsl:template>

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">
<!-- The "root" or "main" template -->
<xsl:template match="/">
<html>
<body>
<!--
| 文本, 属性, 混合XSLT指令:
| <xsl:for-each>, <xsl:value-of>,  等等.
+-->
</body>
</html>
<xsl:template>
</xsl:stylesheet>

或者另一种更简洁的表示:

<!—这种方式,隐藏了匹配根节点的模板 -->
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body>

<!--
| 文本, 属性, 混合XSLT指令:
| <xsl:for-each>, <xsl:value-of>, 等等.
+-->
</body>
</html>

当看到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">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="ROWSET">
<table border="1" cellspacing="0">
<xsl:for-each select="ROW">
<tr>
<td><xsl:value-of select="EMPNO"/></td>
<td><xsl:value-of select="ENAME"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

输出结果:

image

 

上面的模板混合了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>
<body>
<table border="1" cellspacing="0">
<tr>
<td>7839</td>
<td>KING</td>
</tr>
<tr>
<td>7788</td>
<td>SCOTT</td>
</tr>
</table>
</body>
</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">
<body>
<xsl:for-each select="ROWSET">
<table border="1" cellspacing="0">
<xsl:for-each select="ROW">
<tr>
<td><xsl:value-of select="EMPNO"/></td>
<td><xsl:value-of select="ENAME"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</body>
</html>

与前一个相比,写法稍有不一样,但它们输出的结果是彻底同样的,并且后一种看起来更简洁明了。

理解输入和输出选项

在以前的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 -->
<ROWSET>
<ROW>
<EMPNO>7839</EMPNO>
<ENAME>KING</ENAME>
</ROW>
</ROWSET>

用下面的XSLT样式表来转化King.xml,将 <ROWSET>节点转化成 <Invitation>:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Invitation>
<To>
<xsl:value-of select="ROWSET/ROW/ENAME"/>
<xsl:text> &amp; Family</xsl:text>
</To>
</Invitation>
</xsl:template>
</xsl:stylesheet>

 

输出结果:

<?xml version="1.0"?>
<Invitation>
<To>KING &#38; Family</To>
</Invitation>

 

记住,XSLT样式表是格式良好的文档,因此有些特殊字符须要转义,像“&”转义成“&amp;”和“<”转成“&lt;”

还要注意的就是像“&#38;”这样的数值型实体字符,它是数字“38”的Unicode编码形式。若是你习惯这种十六进制的表示,像换行,你能够用&#x0A来代替。

指定output为html, 下面的样式表会将<ROWSET>转化成简单的,包含图片的HTML页面。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<body>
<p>
<xsl:value-of select="ROWSET/ROW/ENAME"/>
<xsl:text> &amp; Family</xsl:text>
</p>
<img src="images/{ROWSET/ROW/EMPNO}.gif"/>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

 

用这个样式表来转化King.xml,输出的结果:

<html>
<body>
<p>KING &#38; Family</p>
<img src="images/7839.gif">
</body>
</html>

若是指定output为text将<ROWSET>转化成文本,那么输出的结果不包含任何标签:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>Hello </xsl:text>
<xsl:value-of select="ROWSET/ROW/ENAME"/>
<xsl:text> &amp; Family,&#x0A;</xsl:text>
<xsl:text>Your id is </xsl:text>
<xsl:value-of select="ROWSET/ROW/EMPNO"/>
</xsl:template>
</xsl:stylesheet>

结果:

Hello King & Family,
Your id is 7839

注意, 咱们转化的样式表中用<xsl:text>来处理文本内容,通常来讲, 空白字符在样式表中是被忽略的, 因此能够实现标签的缩进,以达到更好的可读性。 可是,文本内容中的空格须要被保留,<xsl:text>能够帮助咱们实现这个目的,这样,空白符就会出如今输出的文本中。除了空白字符外,还有换行符和TAB(制表符),利用<xsl:text>元素,这些符号都会被逐字保留。上面例子中的“&#x0A;”表明回车换行符。

下图说明了源文档,源节点树,结果节点树和利用这三部分实现的序列化以及经过指定output,输出不一样的格式。

image

除了outpu属性, 还有其它几个属性能够用来控件输出行为。

image

多模板实现灵活性

咱们都清楚, 样式表包含一组规则, 当咱们用单个模板方式的时候,整个样式表就只有一个规则:“匹配源文件的根节点,执行其中全部的XSLT指令。”这种方式,就像咱们在Java编码的时候,将全部的逻都放在一个main()函数里,确定会有人同意,有人反对。

public class doit {
public static void main() (String[] args) {
// 全部的代码都放在这里}
}

作为开发人员,刚入门的时候会以为将全部实现都放在单个方法里比较容易理解,但他们很快就会发现,当逻辑愈来愈复杂的时候,在单个方法可能有不少能够共用的部分,若是能将它们单独作为一个方法, 能够更好的提供代码的可重用性,多模板也是基本这个考虑。若是采用多模板,咱们也能够利用人家已经实现的规则,就比如站在巨人的肩膀上,可让你节约时间,并且你也能够新建一个本身规则,替换老的。

咱们能够发现,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:output indent="no"/>
<xsl:template match="/">
<html>
<body><xsl:apply-templates/></body>
</html>
</xsl:template>
<xsl:template match="ROWSET">
<table border="1" cellspacing="0"><xsl:apply-templates/></table>
</xsl:template>
<xsl:template match="ROW">
<tr><xsl:apply-templates/></tr>
</xsl:template>
<xsl:template match="EMPNO">
<td><xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="ENAME">
<td><xsl:apply-templates/></td>
</xsl:template>
</xsl:stylesheet>

 

咱们能够举其中的某个模板为例:

<xsl:template match="ROWSET">
<table border="1" cellspacing="0"><xsl:apply-templates/></table>
</xsl:template>

它的含义是: 对于源文档中的<ROWSET>元素, XSLT处理器会应用这个模板来进行转化,该模板会在结果树中构建一个<table>元素,由于模板包含<xsl:apply-templates/>指令,处理器会继续查找<ROWSET>元素的全部子节点,而后用相应节点的模板来转化,直到全部的节点都转化完成,最终,各个节点的转化片断组成大的结果树,作为<table>元素的子元素。

一般状况,处理器会从源文档的根节点开始,搜索匹配的模板,在咱们的样式表中就是match="/"的那个模板,“/”符号就是表示根节点。全部这个根节点就作为当前节点被实例化。根节点模板构造出<html> 和<body> ,而后调用<xsl:apply-templates>去处理根节点的全部子节点。 这些子节点包含一个注释节点和一个元素节点<ROWSET>,为了构造这些节点的结果树片断,处理器按顺序执行全部的子节点。注释节点会被忽略(这个稍后解释),对于<ROWSET>节点,会有相匹配的模板(match等于"ROWSET")来处理。

下面的图说明XSLT处理器的顺序处理的过程。

image

全部的模板都会被实例化,而后输出到结果树中。根节点模板会输出<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="/|*">
      <xsl:apply-templates/>
</xsl:template>

匹配根节点或任何节点,这个内置模板不输出任何东西,但会告诉处理器去执行源文档当前节点下的全部子节点。
<xsl:template match="text()|@*">
       <xsl:value-of select="."/>
</xsl:template>

匹配文本节点或属性节点,将当前节点的值输出。
<xsl:template match="processing-instruction()|comment()"/>

匹配指令节点或注释节点,但什么也不作。

为何须要内置模板,设想一下,若是有人只想匹配源文档中的某个节点,可是默认状况下,XSLT处理器都是从根节点开始匹配,若是没有内置模板,系统会提示模板不存在就会报错,要是每一个开发人员都要将这些模板在本身的样式表中都实现,会使样式表看起来不够简洁。

会了更好的理解内置模板,咱们会用下面的样式表来转化文档”Emp.xml “,该样式表中不包含任何模板:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- This stylesheet contains no rules -->
</xsl:stylesheet>

获得的结果是:

<?xml version = '1.0' encoding = 'UTF-8'?>


7839
KING


7788
SCOTT

处理器用内置的模板来匹配源文档中的元素,对于根节点元素,内置模板不会作任何的输出,只会循环遍历它的子元素,当子元素是空白元素的时候或者”7839“, ”KING“, “7788, 和“SCOTT”这样的文本元素,就会用内置的text()来匹配,调用<xsl:value-of select="."/>来拷贝当前文本节点的元素值到结果树中。 相应的, 结果树就是源文档中的一堆文本内容,来自任何层次的节点,以文档中显示的顺序输出。尽管这颇有意思,但咱们不会将这种不包含任何模板的样式表用在实际的项目中。

通配符匹配和空白字符的处理

让咱们看下下面列出的几个模板:

<xsl:template match="EMPNO">
     <td><xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="ENAME">
     <td><xsl:apply-templates/></td>
</xsl:template>

实际上,这两个模板作一样的事情,它们都用来匹配<ROW>下面的两个不一样的子节点, 而后构建一个表格元素<td>,其中包含相应子节点的转化结果树。可是,咱们要是在<ROW>增长新的节点,叫作<COMPANY>,<DEPTNO>,那是否是咱们还要创建两个新的,相似的模板呢,XSLT为咱们提供了更好的解决方案,通配符。在XPATH表达式中,咱们能够用通配符来指定某个结点下面的全部子节点,像这样”ROW/*“。用这种方式,能够再也不须要为每一个子节点设置一个匹配模板,而只要用一个泛型模板就足够了。

<!-- Match any element child of a ROW -->
<xsl:template match="ROW/*">
<td><xsl:apply-templates/></td>
</xsl:template>

用通用的方式实现的模板,例子以下:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body><xsl:apply-templates/></body>
</html>
</xsl:template>
<xsl:template match="ROWSET">
<table border="1" cellspacing="0"><xsl:apply-templates/></table>
</xsl:template>
<xsl:template match="ROW">
<tr><xsl:apply-templates/></tr>
</xsl:template>
<!-- Match any element child of a ROW -->
<xsl:template match="ROW/*">
<td><xsl:apply-templates/></td>
</xsl:template>
</xsl:stylesheet>

输出的结果与以前的一致,可是样式表看起来更简洁。

image

等等, 好像跟以前的输出比较起来,仍是有不同的地方。

这段文本并无像预期的那样缩进,可是在单模板样式表中,输出的结果是排版良好的,全部节点都有缩进。

明白致使这个问题的缘由很重要,由于这关系到XSLT如何处理源文档中的空白符,回想一下,Emp.xml文档的缩进是经过空白字符和回车符实现的。若是咱们将这些都显现出来的话,应该是下面的样子。

image

当执行匹配<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:strip-space elements="*"/>

与之相反,若是你要保留某个元素的空白节点,须要用<xsl:preservespace>,它一样包含有elements属性。默认状况下,XSLT处理器保留全部元素的空白节点。

不一样模式下的节点处理

前面例子中,输出的结果只有数据信息,并无包含表头,

image

为了建立一个通用的方式来生成表头,咱们必须遍历全部的<ROW>元素,而后取出它子节点的名称作为表格的头单元。然而,咱们已经有了一个匹配“ROW/*”的模板来处理<ROW>元素的子节点,如今为了建立表头,也须要处理这些节点,若是不一样的模板有相同的MATCH属性,XSLT处理会根据优先规则只采用其中的一个,那么如何来区分这两种不一样的应用呢,XSLT为模板提供了另一个属性MODE:

  • 假设咱们指定了MODE属性值为"ColumnHeaders",那么它就与原来没有MODE属性的模板区分开来,它们虽然是处理相同的节点,但逻辑上彻底不一样。

<xsl:template match="ROW/*" mode="ColumnHeaders">
<th>
<xsl:value-of select="name(.)"/>
</th>
</xsl:template>

  • 在用<xsl:apply-templates />调用模板时,必须指定MODE属性,像<xsl:apply-templates mode="ColumnHeaders"/>

<xsl:template match="ROWSET">
<table border="1" cellspacing="0">
<!-- Apply templates in "ColumnHeader" mode first -->
<xsl:apply-templates mode="ColumnHeaders"/>
<!-- Then apply templates to all child nodes normally -->
<xsl:apply-templates/>
</table>
</xsl:template>

可是这样的写法有问题,生成表头的模板会匹配<ROWSET>下面的全部<ROW> 节点,根据<ROW> 节点的个数,会生成重复的表头信息,实际上, 咱们只要处理一个<ROW> 节点就够了。

解决这个问题很是简单,只要保证咱们的XPATH表达式只选择一个<ROW> 节点就行,修改<xsl:apply-templates mode="ColumnHeaders"/>为<xsl:apply-templates select="ROW[1]/*" mode="ColumnHeaders"/>。

image

重用和定制已有的模板

咱们曾经提到过,利用多模板方式,能够重复利用已有的模板规则,甚至能够用新的模板替换老的模板,好比咱们要生成一张相似上面的表格,不一样的是,对于工资大于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">
<!—引用全部的模板 -->
<xsl:import href="TableBaseWithCSS.xsl"/>
<!-- 新建模板来匹配 SAL > 2000 -->
<xsl:template match="ROW[ SAL > 2000 ]">
<tr class="Highlight"><xsl:apply-templates/></tr>
</xsl:template>
</xsl:stylesheet>

当用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">
<!-- 引入样式表"TableBaseWithCSS.xsl"-->
<xsl:import href="TableBaseWithCSS.xsl"/>
<!--匹配DEPTNO 等于20 -->
<xsl:template match="ROW[ DEPTNO = 20 ]">
<tr>
<td align="center" colspan="{count(*)}">
<table border="0">
<tr>
<td>Classified</td>
</tr>
</table>
</td>
</tr>
</xsl:template>
<!--

匹配全部的奇数行-->
<xsl:template match="ROW[ position() mod 2 = 0 ]">
<tr class="Even"><xsl:apply-templates/></tr>
</xsl:template>
<!-- 匹配全部的偶数行-->
<xsl:template match="ROW[ position() mod 2 = 1 ]">
<tr class="Odd"><xsl:apply-templates/></tr>
</xsl:template>
</xsl:stylesheet>

 

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]会优先被处理器使用。

image

 

定义命名模板

接下来,咱们看个格式化数字的例子,下面的样式表有个format-number()函数,它的做用就是将数值转换成指定的格式。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="TableBaseWithCSS.xsl"/>
<!--另一种方式实现隔行处理-->
<xsl:template match="ROW">
<!-- class 属性的值在"tr0" 和"tr1" 变化-->
<tr class="tr{position() mod 2}"><xsl:apply-templates/></tr>
</xsl:template>
<xsl:template match="ROW/SAL">
<td align="right">
<xsl:value-of select="format-number(.,'$0.00')"/>
</td>
</xsl:template>
</xsl:stylesheet>

这里,咱们仍是要引用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 }
th { color: black}
.tr0 {background-color: #f9f9f9; color: black}

若是,你须要常常对数值进行格式化,像是对货币格式的转换,最好是再建一个模板,以方便重用。咱们能够用name属性替换<xsl:template>的match属性。

<xsl:template name="moneyCell">
<td align="right"><xsl:value-of select="format-number(.,'$0.00')"/></td>
</xsl:template>

而后,不管何时咱们想调用模板,只要执行带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的模板跟命名模板的时候,会常常不经意出现错误:

  • 你定义个模板匹配”ROWSET/ROW“ 节点,你可能会错误的使用<xsl:template name="ROWSET/ROW">,正确的写法应该是<xsl:template match="ROWSET/ROW">。
  • 你想应用某个模板,你这么写<xsl:apply-templates match="ROWSET/ROW">,而正解的写法应该是<xsl:apply-templates select="ROWSET/ROW">
相关文章
相关标签/搜索