使用XSLT转换XML

使用XSLT转换XMLSLT1.0是W3C标准,主要用于对XML文档的转换,包括将XML转换成HMTL,TEXT或者另外格式的XML文件.XSLT1.0可以与XPATH1.0标准一起使用,XPATH会告诉你要转换的节点而

大家好,又见面了,我是你们的朋友全栈君。

SLT1.0 是W3C标准,主要用于对XML文档的转换,包括将XML转换成HMTL,TEXT或者另外格式的XML文件.XSLT1.0可以与XPATH1.0标准一起使用,XPATH会告诉你要转换的节点而XSLT则提供了一种机制来说明如何实现这种转换。为了将源文档转换成想要的格式, 一个XSLT样式文件往往包含一系列的规则,而要解释这些规则, 需要依赖XSLT处理器,这个处理器实际上就是对XSLT1.0标准的实现,所以大家可以按照这个标准提供自己的实现,处理器可以根据标准元宝的所有特性来来执行规则,最终完成转换的工作。

XSLT的处理机制

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

下面的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>,大家要清楚根节点跟文档节点的区别),注释节点和指令结点。文档节点可以包含任意数量的文本节点和元素节点,同时,这些子节点仍可以包含其他任意数量的子节点,这些节点以相互嵌套的方式形成了一棵树。

所以,要实现文档转换, 必须要有两个组成部分:

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>

类似,像下面的模板:

<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”>
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/155171.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • ubuntu 20.04 lts安装_vmware如何安装

    ubuntu 20.04 lts安装_vmware如何安装ubuntu22.04lts安装步骤

  • 什么是莫兰指数

    什么是莫兰指数什么是莫兰指数?根据百度百科的定义是“空间自相关系数的一种,其值分布在[-1,1],用于判别空间是否存在自相关。”简单的说就是判定一定范围内的空间实体相互之间是否存在相关关系,比如:一座座居民楼它们是聚集在一块还是离散分布在各处。莫兰指数数值分布在[-1,1],[0,1]说明各地理实体之间存在正相关的关系,[-1,0]之间说明存在负相关的关系,而0值则无相关…

  • 安装激活成功教程版的Pycharm2018.2[通俗易懂]

    安装激活成功教程版的Pycharm2018.2[通俗易懂]Pycharm是什么工具,不用过多解释吧。激活成功教程分四步,步骤如下:一、下载Pycharm2018.2版链接:https://pan.baidu.com/s/1lvf_6iAkXQx49IC54YNbXA提取码:q99kPS:如果自行在官网下载,一定要记住,是下载2018.2版。二、安装并运行,之后关闭PS:一定要记得打开后,再关闭。三、下载激活成功教程补丁…

  • xfire框架内部基本结构解析

    xfire框架内部基本结构解析1概述xfire是webservice的一个实现框架,是apache旗下CXF的前身,是一个比较被广泛使用的webservice框架,网上有很多关于如何使用xfire或cxf的helloworld

  • CentOS搭建msmtp+mutt实现邮件发送

    CentOS搭建msmtp+mutt实现邮件发送

  • Springmvc执行流程介绍[通俗易懂]

    Springmvc执行流程介绍[通俗易懂]1.什么是MVCMVC是ModelViewController的缩写,它是一个设计模式。2.springmvc执行流程详细介绍第一步:发起请求到前端控制器(DispatcherServlet)第二步:前端控制器请求HandlerMapping查找Handler,可以根据xml配置、注解进行查找第三步:处理器映射器HandlerMapping向前端控制器返回Handler第四步:前端控制器调用处理器适配器去执行Handler第五步:处理器适配器去执行Handler第

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号