XSLT入门教程
来源:互联网 发布:2016年各省地税数据 编辑:程序博客网 时间:2024/05/22 03:18
1.XSLT的概念
1.1 什么是XSLT
1.2 为什么要用XSLT
1.3 XSLT的历史
1.4 什么是XPath
1.5 XSLT和CSS的比较
1.6 XSLT和IE5
1.XSLT的概念
我们首先来澄清一个概念,大家可能听说过XSL(eXtensible Stylesheet Language),XSL和我们这里说的XSLT从狭义上理解是一样的,而按照W3C的标准,XSLT的说法更严格些,因此我们在文章中统一使用XSLT的称法。它们之间具体的关系我们会在下面讲述。
1.1 什么是XSLT
XSLT的英文标准名称为eXtensible Stylesheet Language Transformation。根据W3C的规范说明书(http://www.w3.org/TR/xslt),最早设计XSLT的用意是帮助XML文档(document)转换为其它文档。但是随着发展,XSLT已不仅仅用于将XML转换为HTML或其它文本格式,更全面的定义应该是:
XSLT是一种用来转换XML文档结构的语言。
1.2 为什么要用XSLT
我们已经知道,XML是一种电脑程序间交换原始数据的简单而标准的方法。它的成功并不在于它容易被人们书写和阅读,更重要的是,它从根本上解决了应用系统间的信息交换。因为XML满足了两个基本的需求:
(1).将数据和表达形式分离。就象天气预报的信息可以显示在不同的设备上,电视,手机或者其它。
(2).在不同的应用之间传输数据。电子商务数据交换的与日俱增使得这种需求越来越紧迫。
为了使数据便于人们的阅读理解,我们需要将信息显示出来或者打印出来,例如将数据变成一个HTML文件,一个PDF文件,甚至是一段声音;同样,为了使数据适合不同的应用程序,我们必须有能够将一种数据格式转换为另一种数据格式,比如需求格式可能是一个文本文件,一个SQL语句,一个HTTP信息,一定顺序的数据调用等。而XSLT就是我们用来实现这种转换功能的语言。将XML转换为HTML,是目前XSLT最主要的功能。
1.3 XSLT的历史
想很多其他XML家族成员一样,XSLT是由W3C起草和制定的。它的主要发展历程如下:
.1995年由James Clark提议;
.1997年8月正式提案为XSL;
.1998年5月由Norman Walsh完成需求概要;
.1998年8月18日草案发布;
.1999年11月16日正式发布XSL 1.0推荐版本。
目前,XSLT仍然在快速的发展中,XSLT1.1的草案已经可以在W3C网站(http://www.w3.org/TR/xslt11)上看到。
1.4 什么是XPath
XPath是XSLT的重要组成部分,我们将在第四章讲解它的详细语法。那么XPath是什么呢?我们首先来了解一下XSL系列的"家族"关系。如下图:
XSL在转换XML文档时分为明显的两个过程,第一转换文档结构;其次将文档格式化输出。这两步可以分离开来并单独处理,因此XSL在发展过程中逐渐分裂为XSLT(结构转换)和XSL-FO(formatting objects)(格式化输出)两种分支语言,其中XSL-FO的作用就类似CSS在HTML中的作用。而我们这里重点讨论的是第一步的转换过程,也就是XSLT。
另外,在学习XML时我们已经知道XML是一个完整的树结构文档。在转换XML文档时可能需要处理其中的一部分(节点)数据,那么如何查找和定位XML文档中的信息呢,XPath就是一种专门用来在XML文档中查找信息的语言。XPath隶属XSLT,因此我们通常会将XSLT语法和XPath语法混在一起说。
用一种比较好理解的解释:如果将XML文档看作一个数据库,XPath就是SQL查询语言;如果将XML文档看成DOS目录结构,XPath就是cd,dir等目录操作命令的集合。
1.5 XSLT和CSS的比较
CSS同样可以格式化XML文档,那么有了CSS为什么还需要XSLT呢?因为CSS虽然能够很好的控制输出的样式,比如色彩,字体,大小等,但是它有严重的局限性,就是:
(1) CSS不能重新排序文档中的元素;
(2) CSS不能判断和控制哪个元素被显示,哪个不被显示;
(3) CSS不能统计计算元素中的数据;
换句话说,CSS只适合用于输出比较固定的最终文档。CSS的优点是简洁,消耗系统资源少;而XSLT虽然功能强大,但因为要重新索引XML结构树,所以消耗内存比较多。
因此,我们常常将它们结合起来使用,比如在服务器端用XSLT处理文档,在客户端用CSS来控制显示。可以减少响应时间。
1.6 XSLT和IE5
在XSLT草案发布不久,微软就在IE4中提供了支持XSL功能的预览版本,到IE5.0发布时,正式全面支持XSLT,可是由于IE5发布的比XSLT1.0标准时间早,因此在IE5.0中支持的XSTL功能和XSLT 1.0略有不同。(呵呵~~XML推行的主要原因之一就是解决HTML过分依赖浏览器的问题,现在微软又想标新立异?)。好在微软的IE5.5中执行的标准已经和W3C的XSLT1.0基本相近。但令人头疼的是IE5.0已经发行了几百万套,您使用的XSLT很可能不能被客户的浏览器正确执行。目前XSLT 1.1仍在发展中,W3C及有关组织也在和微软协商争取获得统一。呵呵~~故事还远远没有结束噢。
注意:本文中提到的语法都是根据XSLT 1.0的标准来讲的,没有任何微软的"方言"。XSL
第1章 入门篇:带领初学者开始XSLT的旅行
简单地说,XSLT就是把XML翻译成HTML文档并使其能够在浏览器中显示的一种转换规则。实际上,XSLT的作用并不仅仅能够XML到HTML的转换,它还有许多其他的作用。
1 前言
一般来说,XML文档中没有样式信息(如字体等)。我们必须指定样式信息才能用适当的形式来显示XML文档。XSL(Extensible Stylesheet Language)就是能够应用在XML文档中的一种样式描述语言。
XSL有以下两种。
XSLT(XSL Transformations)
将某个XML文档变换成其他的XML文档
FO(Formatting Objects)
定义文档的阅读方法
FO的标准尚未确定。XSLT的1.0版是W3C推荐的标准。由于XSLT是XML文档转换语言,虽然不能直接定义显示方式,但可以通过将XML文档转换成能够显示的格式,以达到指定样式的目的。将来,也许能通过XSLT将XML转换成容易显示的格式,而且可以通过FO将XML转换成阅读方法的定义。
虽然说是XML文档转换,但由于XSLT的目的是指定样式,因此并不能说是完全的通用转换语言。然而,XML文档转换的许多场合下都可以使用XSLT。
2 第一步
首先让我们看一个简单的例子,来熟悉一下XSLT样式表的书写方式。被转换的XML文档如代码1所示。1。
代码1: hello.xml
<?xml version="1.0"?>
<document>
<content>你好!</content>
</document>
顶层元素为“document”,其下有一个子元素“content”。将该XML文档转换为HTML文档的XSLT样式表如代码2所示。
代码2: hello.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="document">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="content">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
XSLT样式表也是XML文档,因此第1行写出了XML声明。第2行为XSLT样式表的根元素。在这里指定了命名空间。在XSLT样式表中,XSLT元素和作为输出结果的元素是混杂在一起书写的,因此必须通过命名空间来区分XSLT的命名空间的URI为“http://www.w3.org/1999/XSL/Transform”。
xsl:template元素定义了对各个节点的处理方式,称为模板。在表2中定义了两个模板。
<xsl:template match="document">
该标记标志着对document元素的模板的开始。match属性中指定的字符串为被转换的XML文档中包含的元素。对于document元素的模板为首先输出HTML标记<html>和<body>。其后的xsl:apply-templates元素指明,应用document元素的子元素所对应的模板。
<xsl:apply-templates/>
之后输出</body>和</html>标记。最后是xsl:template的结束标记。
</xsl:template>
这样就能够定义一个模板规则。由于在针对document元素的模板中指明了要应用针对子元素的模板,因此需要定义针对子元素的模板。document的子元素是content,因此针对该标记定义模板。
针对content元素的模版中首先输出HTML标记<p>,然后输出content元素的内容。
<xsl:value-of select="."/>
xsl:value-of元素将select属性指定的节点作为字符串输出。“.”可以认为表示元素本身(这里在定义content的模版,因此可认为是content元素)。最后输出</p>标记,结束。
XSLT分析器分析的结果如代码3所示。2。
代码3: hello.html
<html>
<body>
<p>你好!</p>
</body>
</html>
3 结语
我们介绍了非常简单的一个XSLT样式表,应该能抓住概要了吧。创建XSLT样式表,其中心就是定义模板。即使仅创建简单的模版并把它们组合起来,也能做到非常精妙的转换。但是,XSLT的元素并不仅仅是这些。第2章就来介绍XSLT的各种元素。
注
1本文中未特别注明的文字编码均为UTF-8。关于UTF-8编辑器,请参见文件column1.txt。另外,为了使代码更具可读性,有可能会改变代码的换行和缩进。
2本文的内容使用Xalan作为XSL处理器进行确认。关于使用Xalan等,请参见文件column2.txt。另外,本文并未将XSLT的输出结果直接给出,而是为了增加可读性,调整了其中的换行和缩进等。
第2章 基础篇: XSLT快速参考
1 前言
在第1章中,我们用一个简单的XSLT样式表作为例子介绍了XSLT的概要。当然,XSLT所拥有的元素并不仅限于xsl:template、xsl:apply-templates、xsl:value-of。为了实行转换,XSLT准备了各种各样的元素。本章将以最常用的元素为中心举例说明。
另外,本文中并不准备介绍元素的全部属性。详情请参见W3C标准的原文[1]及译文[2]等。
在XSLT中经常会用到一种被称为XPath的描述方法。首先从XPath的概要开始介绍。
2 XPath是什么?
XPath是树结构的一种描述方法。在创建XSL样式表时经常使用XPath。
2.1 树结构
XML文档表示的数据组成数结构。用XLST进行转换,也就是从源文档的树(源树)生成转换后的树(目标树)的意思。这个转换以树结构的节点为基础来进行。
节点有几种,主要为:
- 表示根的“根节点”
- 表示元素的“元素节点”
- 表示属性的“属性节点”
- 表示文本的“文本节点”
- 表示注释的“注释节点”
使用像这样的各种各样的节点来表示树的位置的描述方法称为XPath。
2.2 XPath表达式
创建XSLT样式表时经常使用的XPath表达式如表1所示。所谓的上下文节点,就是由上下文构成的节点,可以理解为“处理对象”。另外,今后会出现“当前节点”的概念,可以理解为“被选中的节点”。 1
表1: 常用的XPath表达式
记号
含义
a
上下文节点的a元素
*
上下文节点的所有元素
a/b
以上下文节点的a元素为父节点的b元素
a//b
以上下文节点的a元素为祖先的b元素
a|b
上下文节点的a元素和b元素
a[表达式]
符合表达式的上下文节点的a元素
.
上下文节点
..
上下文节点的父节点
/
根节点
@a
上下文节点的a属性
@*
上下文节点的所有属性
node()
所有节点
text()
文本节点
描述类似于UNIX的路径描述。例如,
<html>
<body>
<a href="sample.html">示例</a>
<ul>
<li>项目A</li>
<li>项目B</li>
</ul>
</body>
</html>
中,表示元素a的XPath表达式为
/html/body/a
此外,若表示a元素的href属性,则为
/html/body/a/@href
表中a[表达式]是符合表达式的a项目的意思,例如表示第一个li项目时使用
/html/body/ul/li[1]
3 定义样式表的元素
XSLT样式表使用XML文档的格式创建。因此,必须要遵从XML文档的描述规则。XML文档中必须存在的元素只有根元素。在XSLT样式表中的根元素就是xsl:stylesheet元素。基本代码如下所示:
<xsl:stylesheet
version = "版本号">
<!-- 内容: (xsl:import*, 顶层元素) -->
</xsl:stylesheet>
4 模板规则
XSLT样式表可以说是模板规则的集合。
4.1 模板规则的定义
模板规则使用xsl:template元素进行定义。它的属性包括match、name、priority和mode。其中最重要的是match属性,该属性规定了节点的样式。若没有指定name属性的话,就必须指定match属性。基本的代码如下所示。
<xsl:template
match = "样式"
name = "名称">
<!-- 内容: (xsl:param*, 模板) -->
</xsl:template>
name属性将在调用命名模板时使用。关于命名模版将在后面讲述。
4.2 应用模板规则
我们使用xsl:apply-template元素来应用模板规则。它包含select和mode属性。基本的代码如下所示:
<xsl:apply-templates
select = "节点集合表达式">
<!-- 内容: (xsl:sort | xsl:with-param)* -->
</xsl:apply-templates>
后面将讲到的排序操作等之外的情况下该元素不需要内容,因此空元素标记可以写成以下的形式:
<xsl:apply-templates select="节点集合表达式"/>
未指定select属性时,当前节点为所有的子节点。
通常,仅在处理当前节点的子孙节点时使用该元素。这样就不会发生无法终了的无限循环。不能定义如下例所示的无限循环模版:
<xsl:template match="x">
<xsl:apply-templates select="."/>
</xsl:template>
4.3 命名模版
带有name属性的模板规则可以通过模版名称来调用。
<xsl:call-template
name = "名称">
<!-- 内容: xsl:with-param* -->
</xsl:call-template>
例如,定义下例所示的模板规则时,
<xsl:template name="hello">
你好
</xsl:template>
可以以下面的方式调用:
<xsl:call-template name="hello"/>
输出为:
你好
4.4 匹配冲突
某个指定的表达式可能会出现多个匹配结果。这时根据优先度来决定应用哪个模板。定义模板规则时可通过设置priority属性来显式地指定模板优先度。如未指定,将采用默认优先度。关于默认优先度的计算方法请参见参考文献[1]。一般来说限制性强的表达式优先度较高。例如“a”的优先度要高于“*”。
4.5 内嵌模版规则
内嵌模板规则即为默认模板规则,不匹配任何模板规则的节点将由它来处理。这种机制保证了即使在不定义任何模板规则的情况下,模版处理也能递归地进行下去。
应用于元素节点和根节点的内嵌模板规则如下所示。
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
应用于文本节点和属性节点的内嵌模板规则如下所示。
<xsl:template match="text()|@*">
<xsl:value-of select="."/>
</xsl:template>
对于其他节点(如注释节点等)不做任何处理。
这种模板规则将把文本节点内容全部输出。将代码1所示的XSLT样式表应用到某个XML文档上之后,将生成以doc为根节点、文本节点为内容的XML文档。
代码1: builtin.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<doc>
<xsl:apply-templates/>
</doc>
</xsl:template>
</xsl:stylesheet>
5 输出
虽然称为“输出”,但实际的意思是“生成目标树的节点”。可以使用源树生成节点,也可以直接生成节点。
5.1 取出文本
xsl:value-of元素可以将指定的节点的值作为字符串来输出。必须要指定select属性。
<xsl:value-of
select = "字符串表达式"/>
指定的树种包含其它元素时,文本节点以外的节点将被忽略,仅输出文本。例如,
<p>.com时代的<b>IT</b>杂志</p>
对该树应用
<xsl:value-of select="p"/>
时,输出为
.com时代的IT杂志
选择当前节点可以使用“.”。若将
<a href="http://www.gihyo.co.jp">
技术评论公司</a>
作为当前节点,那么
<xsl:value-of select="."/>的URL是
<xsl:value-of select="@href"/>。
的输出为
技术评论公司的URL是
http://www.gihyo.co.jp。
5.2 节点复制
xsl:value-of元素将节点的值转换为字符串。与此相对,xsl:copy-of元素将复制节点,节点中包含的子元素原封不动。基本的代码如下所示。
<xsl:copy-of
select = "表达式"/>
例如、
<p>.com时代的<b>IT</b>杂志</p>
对此应用
<xsl:copy-of select="p"/>
的话,输出为
<p>.com时代的<b>IT</b>杂志</p>
5.3 属性值模板
需要生成属性值时,可以使用大括号{}将表达式括起来。2。
例如,想由
<link>
<title>技术评论公司</title>
<url>http://www.gihyo.co.jp/</url>
</link>
生成如下的HTML链接时,
<a href="http://www.gihyo.co.jp/">
技术评论公司
</a>
可以定义如下的模板规则。
<xsl:template match="link">
<a href="{url}">
<xsl:value-of select="title"/>
</a>
</xsl:template>
5.4 生成文本
xsl:text元素可以生成文本节点。由于生成文本时直接将文本写出即可,所以一般情况下该元素不使用。但是若需要令输出的转义字符失效的话就需要使用该元素。基本代码如下所示。
<xsl:text
disable-output-escaping = "yes" | "no">
<!-- 内容: #PCDATA -->
</xsl:text>
通过disable-output-escaping属性可以指定输出转义字符是否无效。默认值是no。指定为on时,
<xsl:text disable-output-escaping="yes">
<
</xsl:text>
的输出为
<
5.5 生成注释
需要生成注释时可以使用xsl:comment元素。
<xsl:commwnt>这里是注释。</xsl:comment>
的输出如下所示。
<!--这里是注释。-->
5.6 复制
复制当前节点可以使用xsl:copy元素。属性和子节点不会被自动复制。基本代码如下所示。
<xsl:copy>
<!-- 内容:模板 -->
</xsl:copy>
例如,将没有属性的上下文元素属性原样输出,可以使用如下的模板规则。
<xsl:template match="content">
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
若需要递归地复制所有节点,可以使用如下的模板规则。
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
5.7 生成元素
需要生成元素时可以使用xsl:element元素。通常,只要将需要生成的元素直接写出即可,不必使用xsl:element元素。该元素仅在需要动态生成元素时才有必要使用。基本代码如下所示。
<xsl:element
name = "生成元素的名称">
<!-- 内容: 模板 -->
</xsl:element>
例如,若要由
<heading>
<text>这是标题。</text>
<size>2</size>
</heading>
生成HTML标题(h元素),则可以使用如下的模板规则。
<xsl:template match="heading">
<xsl:element name="{concat('h', size)}">
<xsl:value-of select="text"/>
</xsl:element>
</xsl:template>
concat()是字符串连接函数。从size元素中取出值并将其连接在“h”之后构成元素名。输出如下所示。
<h2>这是标题。</h2>
5.8 生成属性
需要生成属性时可以使用xsl:attribute元素。与生成元素的情况相同,通常只需要将需要生成的属性直接写出即可,不需使用xsl:attribute元素。该元素仅在需要动态生成属性等场合才有必要使用。基本代码如下所示。
<xsl:attribute
name = "生成属性的名称">
<!-- 内容:模板 -->
</xsl:attribute>
例如3 ,若想由
<link>
<title>技术评论公司</title>
<url>http://www.gihyo.co.jp/</url>
</link>
生成如下的HTML链接时,
<a href="http://www.gihyo.co.jp/">
技術評論社
</a>
可以定义如下的模板规则。
<xsl:template match="link">
<a>
<xsl:attribute name="href">
<xsl:value-of select="url"/>
</xsl:attribute>
<xsl:value-of select="title"/>
</a>
</xsl:template>
5.9 添加编号
需要输出整数值时可以使用xsl:number元素。基本代码如下所示。
<xsl:number
level = "源树的级别"
count = "表达式"
from = "表达式"
value = "数值表达式"
format = "表示格式的字符串" />
首先看看最简单的例子。
<items>
<item>A</item>
<item>D</item>
<item>B</item>
<item>C</item>
</items>
对该XML文档定义如下的模板规则。
<xsl:template match="items">
<xsl:copy>
<xsl:apply-templates select="item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:number/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
生成结果如下所示。
<items>
<item>1 A</item>
<item>2 D</item>
<item>3 B</item>
<item>4 C</item>
</items>
省略value属性时,则根据源树内当前节点的位置来输出数值。由于这个原因,若使用后面将要讲到的排序元素的话,即如果定义如下所示的模板规则的话,
<xsl:template match="items">
<xsl:copy>
<xsl:for-each select="item">
<xsl:sort select="."/>
<xsl:copy>
<xsl:number/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
输出结果将如下所示。
<items>
<item>1 A</item>
<item>3 B</item>
<item>4 C</item>
<item>2 D</item>
</items>
需要按照排序结果的顺序来添加编号的话,则须按照如下方式指定value属性。position()函数返回正在处理的上下文节点的位置。
<xsl:number value="position()"/>
可以使用level属性、count属性、from属性来控制编号方法。count属性设置应当被编号的节点。level属性设置作为编号对象的源树的级别。可指定的级别有single、multiple、any三种,默认值为single。
例如,有下面这种层次结构的XML文档。
<chapter title="第一章">
<section title="第一节">
<subsection title="第一部分">
</subsection>
</section>
<section title="第二节">
<subsection title="第二部分">
</subsection>
<subsection title="第三部分">
</subsection>
</section>
</chapter>
将xsl:number元素的level属性设置为single,即将如下的模板规则应用于该文档,
<xsl:template match="chapter|section|subsection">
<xsl:apply-templates select="@title"/>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="@title">
<xsl:number level="single"
count="chapter|section|subsection"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/><br/>
</xsl:template>
转换结果如下所示。
1 第一章<br/>
1 第一节<br/>
1 第一部分<br/>
2 第二节<br/>
1 第二部分<br/>
2 第三部分<br/>
也就是输出在兄弟节点中当前节点的位置。设置level属性为multiple时,转换结果如下所示。
1 第一章<br/>
1.1 第一节<br/>
1.1.1 第一部分<br/>
1.2 第二节<br/>
1.2.1 第二部分<br/>
1.2.2 第三部分<br/>
编号与single相同,但是增加了父节点的编号。设置level属性为any时,转换结果如下所示。
1 第一章<br/>
2 第一节<br/>
3 第一部分<br/>
4 第二节<br/>
5 第二部分<br/>
6 第三部分<br/>
也就是按照符合count指定的表达式的节点的顺序输出序号。
from属性设置计数开始的位置。例如,考虑如下的XML文档。从逻辑上看,各个节点之间有父子关系,但是在源树中各个节点是并列关系。
<h1>大标题一</h1>
<h2>中标题一</h2>
<h3>小标题一</h3>
<h2>中标题二</h2>
<h3>小标题二</h3>
<h3>小标题三</h3>
下面使用from属性设置计数开始的节点。
<xsl:template match="h1">
<xsl:number/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/><br/>
</xsl:template>
<xsl:template match="h2">
<xsl:number level="any" count="h1"/>
<xsl:text>.</xsl:text>
<xsl:number level="any" from="h1" count="h2"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/><br/>
</xsl:template>
<xsl:template match="h3">
<xsl:number level="any" count="h1"/>
<xsl:text>.</xsl:text>
<xsl:number level="any" from="h1" count="h2"/>
<xsl:text>.</xsl:text>
<xsl:number level="any" from="h2" count="h3"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/><br/>
</xsl:template>
转换结果如下所示。
1 大标题一<br/>
1.1 中标题一<br/>
1.1.1 小标题一<br/>
1.2 中标题二<br/>
1.2.1 小标题二<br/>
1.2.2 小标题三<br/>
使用format属性设置输出文字的格式。默认的格式为“1”,即输出数字。用于表示格式的字符串如表2所示。
表2: 格式字符串
字符串
格式范例
1
1 2 ... 10 11 ...
01
01 02 ... 09 10 ... 99 100 ...
A
A B ... Z AA AB ...
a
a b ... z aa ab ...
i
i ii iii iv v vii viii ix x ...
I
I II III IV V VII VIII IX X ...
我们上面的例子中为了在xsl:number元素之后加一个空格,不得不使用了xsl:text元素。实际上只要将format属性设置为“1 ”就可以了。level属性为multiple的情况下也可以指定类似于“1.A.I”这样的格式。
6 循环
需要循环进行处理时可以使用xsl:for-each元素。基本代码如下所示。
<xsl:for-each
select = "节点集合">
<!-- 内容:(xsl:sort*, 模板) -->
</xsl:for-eaxh>
必须设置select属性,来指定被处理的节点集合。如果使用了后面讲述的排序的话则按照排序结果的顺序进行处理,否则按照节点出现顺序进行处理。
例如,如下所示,bookmark元素中包含了多个link元素。
<bookmark>
<link>
<title>技术评论公司</title>
<url>http://www.gihyo.co.jp</url>
</link>
<link>
<title>ONGS</title>
<url>http://www.ongs.gr.jp</url>
</link>
</bookmark>
利用下面使用了xsl:for-each元素的模板规则,可以将其转换成HTML表格(图1)。
<xsl:template match="bookmark">
<table border="1">
<xsl:for-each select="link">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="url"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
图1: 使用表格进行显示
但是,使用如下所示的模板规则,不用xsl:for-each元素也能得到同样的输出结果。
<xsl:template match="bookmark">
<table border="1">
<xsl:apply-templates select="link"/>
</table>
</xsl:template>
<xsl:template match="link">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="url"/></td>
</tr>
</xsl:template>
仅定义模板规则有时候很难进行转换。例如,需要将行和列交换生成如图2所示的表时,不使用xsl:for-each元素就很难做到。
图2: 行列交换
使用xsl:for-each元素可以生成如图2所示的输出结果。
<xsl:template match="bookmark">
<table border="1">
<tr>
<xsl:for-each select="link/title">
<td><xsl:value-of select="."/></td>
</xsl:for-each>
</tr>
<tr>
<xsl:for-each select="link/url">
<td><xsl:value-of select="."/></td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
7 条件处理
在XSLT中,进行条件处理的元素有xsl:if和xsl:choose两个。xsl:if进行“如果~就~”的if-then型处理,xsl:choose当有多个选择项存在时进行处理。
7.1 xsl:if
xsl:if元素拥有test属性,可指定逻辑表达式。逻辑表达式为真的情况下执行模板的转换,假的情况下不进行转换。
<xsl:if
test = "逻辑表达式">
<!-- 内容: 模板 -->
</xsl:if>
例如,对以下XML文档,
<people>
<person>
<name>张三</name>
<age>30</age>
</person>
<person>
<name>李四</name>
<age>20</age>
</person>
<person>
<name>周五</name>
<age>10</age>
</person>
</people>
使用如下所示的模板进行变换。
<xsl:template match="people">
<ul>
<xsl:apply-templates select="person"/>
</ul>
</xsl:template>
<xsl:template match="person">
<li>
<xsl:value-of select="name"/>今年
<xsl:value-of select="age"/>岁
<xsl:if test="age[.< 18]">
(未成年)
</xsl:if>
</li>
</xsl:template>
<是<的意思(>是>)。age元素的值不足18的情况下输出“(未成年)”。输出结果如下所示。
<ul>
<li>张三今年30岁</li>
<li>李四今年20岁</li>
<li>周五今年10岁(未成年)</li>
</ul>
7.2 xsl:choose
xsl:choose元素由一个或一个以上的xsl:when元素和可选的xsl:otherwise元素组成。
<xsl:choose>
<xsl:when test="逻辑表达式">
<!-- 内容: 模板 -->
</xsl:when>
<xsl:otherwise>
<!-- 内容: 模板 -->
</xsl:otherwise>
</xsl:choose>
与xsl:if元素相同,test属性的逻辑表达式为真的情况下执行指定的模板。从上到下依次测试xsl:when元素,但只有第一个逻辑表达式为真的xsl:when院所的模板会被执行。如果所有的xsl:when元素的逻辑表达式均为假,则执行xsl:otherwise元素所指定的模板。xsl:otherwise元素不存在时则不执行任何模板。
例如,对于如下文档,
<ol>
<li>起床</li>
<li>洗脸</li>
<li>吃早饭</li>
<li>上班</li>
</ol>
应用如下所示的模板。last()函数返回被处理的内容的大小。
<xsl:template match="ol">
<morning>
<xsl:for-each select="li">
<todo>
<xsl:choose>
<xsl:when test="position()=1">
<xsl:text>首先</xsl:text>
</xsl:when>
<xsl:when test="position()=last()">
<xsl:text>最后</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>第</xsl:text>
<xsl:value-of select="position()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="."/>
</todo>
</xsl:for-each>
</morning>
</xsl:template>
输出结果如下所示。
<morning>
<todo>首先起床</todo>
<todo>第2洗脸</todo>
<todo>第3吃早饭</todo>
<todo>最后上班</todo>
</morning>
8 排序
为将数据排序,可以在xsl:apply-templates元素或xsl:for-each元素的子节点中增加xsl:sort元素。xsl:sort元素的基本代码如下所示。
<xsl:sort
select = "字符串表达式"
data-type = "数据类型"
order = "顺序"/>
通过select属性指定排序的节点。通过data-type属性指定字符串的数据类型。设置为text就能够按照人们默认的字典顺序进行排序。设置为number将把字符串看作数字,按照数字的值进行排序。order属性设置排序的顺序。升序为ascending、降序为descending。未指定时默认为升序。其他能够设置的属性包括设置语言的lang属性,设置大小写字母哪个优先的case-order属性等。
例如,将如下所示的由姓名(name)、年龄(age)组成的人(person)的数据按照姓名和年龄进行排序。
<people>
<person>
<name>张三</name>
<age>30</age>
</person>
<person>
<name>李四</name>
<age>20</age>
</person>
<person>
<name>周五</name>
<age>10</age>
</person>
</people>
下面是按照年龄升序、姓名降序进行排序并生成HTML表格的模板规则。
<xsl:template match="people">
<h1>按照年龄升序排列</h1>
<table border="1">
<xsl:apply-templates select="person">
<xsl:sort select="age"
data-type="number"/>
</xsl:apply-templates>
</table>
<h1>按照姓名降序排列</h1>
<table border="1">
<xsl:apply-templates select="person">
<xsl:sort select="name" data-type="text" order="descending"/>
</xsl:apply-templates>
</table>
</xsl:template>
<xsl:template match="person">
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="age"/></td>
</tr>
</xsl:template>
输出结果如图3所示。
图3: 排序结果
与此相同,使用xsl:for-each元素重新书写代码则如下所示。要注意,在将xsl:sort元素作为xsl:for-each元素的子节点使用时,必须将其写在开头的地方。
<xsl:template match="people">
<h1>按照年龄降序排列</h1>
<table border="1">
<xsl:for-each select="person">
<xsl:sort select="age" data-type="number"/>
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="age"/></td>
</tr>
</xsl:for-each>
</table>
<h1>排序</h1>
<table border="1">
<xsl:for-each select="person">
<xsl:sort select="name" data-type="text" order="descending"/>
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="age"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
9 变量
在XSLT中可以使用变量。
9.1 绑定变量
可以为变量赋值的元素有xsl:variable元素和xsl:param元素。
9.1.1 xsl:variable元素
基本代码如下所示。
<xsl:variable
name = "变量名"
select = "表达式">
<!-- 内容: 模板 -->
</xsl:variable>
使用name属性指定变量名(必须)。select属性和内容都不存在的话可以写成空字符串。
<xsl:variable name="x"/>
这种写法与下面的写法意思相同。
<xsl:variable name="x" select="''"/>
通过select属性可以指定变量中代入的值。使用select属性给变量赋值的话,内容必须为空。例如给变量x赋值为2时的代码如下所示。
<xsl:variable name="x" select="2"/>
这种情况下,使用
<xsl:value-of select="item[$x]"/>
将输出第二个item元素。
通过内容来赋值时,变量的值不是数值,而是结果树的片断。关于结果树的片断请参照参考文献[1]。
<xsl:variable name="x">2</xsl:variable>
这种情况下,使用
<xsl:value-of select="item[$x]"/>
不会输出第二个item元素,而是输出了第一个元素。使用值为结果树片断的变量时应当像下面这样书写代码。
<xsl:value-of select="item[position()=$x]"/>
9.1.2 xsl:param元素
xsl:variable元素和xsl:param元素基本上相同。一个区别是,XML解释器假定xsl:param变量中保存着默认值,并可以使用xsl:with-param元素来向模板中传值。详细情况请参见参考文献[1]。
9.2 访问变量
在表达式中访问变量时,需要在变量名前加上$符号。使用xsl:value-of元素访问变量则可以输出变量的值。
10 指定输出格式
XSLT处理器的任务是从XML文档转换成新的XML文档,但是也能够输出XML文档之外的格式。通过xsl:output元素可以指定输出格式。该元素只能作为顶层元素使用。基本代码如下所示。
<xsl:output
method = "输出格式"
version = "版本"
encoding = "编码"
omit-xml-declaration = "yes" | "no"
standalone = "yes" | "no"
indent = "yes" | "no" />
通过method指定输出格式。可以指定的格式包括html、html、text等。未指定时默认为xml,但如果满足以下条件则为html。
- 结果树的根节点的元素名为html(不区分大小写),并且含有子元素。
- 不包含命名空间的URI。
- 结果树的根节点的子元素之前出现的文本只包含空白。
通过version可以指定输出格式的版本。通过encoding指定输出时使用的字符编码。通过omit-xml-declaration来指定是否省略XML定义。通过standalone指定是否输出独立文档的声明。通过indent指定输出结果树时是否使用缩进的格式。
例如,输出i-mode能够浏览的HTML时,可以如下书写代码。由于i-mode只能使用Shift_JIS编码,因此需要按下面的方式定义。
<xsl:output method="html" encoding="Shift_JIS" indent="no"/>
注释
1该表达式并不一定正确。详细情况请参见参考文献[5][6]。
2并不是所有的属性都能够使用属性值模板来处理。详细情况请参见参考文献[1]。
3实际上该例子作为动态生成的例子并不恰当。
第3章 实践篇: XSLT实例
概要
在本章,我们将介绍XSLT样式表的四个例子。
1 复制
这是个复制所有节点的例子(程序1)。例如从XHTML变换成XHTML等情况,源和结果的元素大部分相同时,就可以将该实例稍作修改,仅针对需要改动的部分书写代码即可。
程序1: copy.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
使用xsl:xcopy元素将各节点进行递归复制。
2 行列交换
该实例可以将HTML表格的行和列进行交换(程序2)。
程序2: table.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="table">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:for-each select="tr[1]/td">
<tr>
<xsl:variable name="x" select="last()"/>
<xsl:variable name="y" select="position()-1"/>
<xsl:for-each select="../../tr/td">
<xsl:if test="(position()-1) mod $x = $y">
<td>
<xsl:value-of select="."/>
</td>
</xsl:if>
</xsl:for-each>
</tr>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
因为是交换行列,所以只需输出源表格的列数的行就可以了。在这里,使用
<xsl:for-each select="tr[1]/td">
循环,按照第一行的列数来新建行(tr元素)。使用如下循环
<xsl:for-each select="../../tr/td">
来取出各行中将要保存的数据。由于当前节点是tr[1]/td,那么使用../tdと将仅能取出第一行的td元素。为了取出所有td元素,须写成../../tr/td的形式。
变量x中保存新建表格的总行数,变量y中保存当前正在生成的行的行数-1。因此,
<xsl:if test="(position()-1) mod $x = $y">
使用该条件输出td元素中的数据。mod是取余数的运算符。
将如图1所示的页面(程序3)进行转换,可以得到如图2所示的输出结果。
图1: 输入页面
程序3: table.xml
<?xml version="1.0"?>
<html>
<head>
<title>表格</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
</head>
<h1>表格</h1>
<body>
<table border="1">
<tr>
<td>甲</td>
<td>乙</td>
<td>丙</td>
<td>丁</td>
<td>戊</td>
</tr>
<tr>
<td>己</td>
<td>庚</td>
<td>辛</td>
<td>壬</td>
<td>癸</td>
</tr>
</table>
</body>
</html>
图2: 输出页面
3 表格
表格可以看作是二维数组。让我们试着将它转换成一维数组。但是仅仅将表格内容排列出来就失去了意义,因此我们将保留原表格的行号和列号作为结果的属性值(程序4)。
程序4: databank.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<databank>
<xsl:apply-templates select=".//table"/>
</databank>
</xsl:template>
<xsl:template match="table">
<xsl:for-each select="tr">
<xsl:variable name="row" select="position()"/>
<xsl:for-each select="td">
<xsl:variable name="column" select="position()"/>
<data>
<xsl:attribute name="row">
<xsl:copy-of select="$row"/>
</xsl:attribute>
<xsl:attribute name="column">
<xsl:copy-of select="$column"/>
</xsl:attribute>
<xsl:value-of select="."/>
</data>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
将行号和列号的信息保存到变量row和column中。 。
<xsl:variable name="row" select="position()"/>
<xsl:variable name="column"select="position()"/>
使用row属性输出行号,column属性输出列号。
<xsl:attribute name="row">
<xsl:copy-of select="$row"/>
</xsl:attribute>
<xsl:attribute name="column">
<xsl:copy-of select="$column"/>
</xsl:attribute>
将程序3进行转换时,输出如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<databank>
<data row="1" column="1">甲</data>
<data row="1" column="2">乙</data>
<data row="1" column="3">丙</data>
<data row="1" column="4">丁</data>
<data row="1" column="5">戊</data>
<data row="2" column="1">己</data>
<data row="2" column="2">庚</data>
<data row="2" column="3">辛</data>
<data row="2" column="4">壬</data>
<data row="2" column="5">癸</data>
</databank>
4 将表格转换为CSV文件
虽然XSLT一般用来将XML文档转换成XML文档,但是也能够输出XML文档之外的格式。下面是将含有table元素的HTML文档转换成CSV(Comma Separated Value)格式1输出的例子(程序5)。
程序5: csv.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:apply-templates select=".//tr"/>
</xsl:template>
<xsl:template match="tr">
<xsl:for-each select="td">
<xsl:value-of select="."/>
<xsl:if test="not(position()=last())">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
为输出文本格式,需要指定
<xsl:output method="text" encoding="UTF-8"/>
由于被转换的对象节点是表示行的tr元素,因此针对tr元素定义模板规则。若不是行中的最后一个元素,则输出分隔符“,”。
<xsl:if test="not(position()=last())">
<xsl:text>,</xsl:text>
</xsl:if>
将程序3进行转换的结果如下所示。
甲,乙,丙,丁,戊
己,庚,辛,壬,癸
注释
1记录中的各个数据域之间使用分隔符“,”进行分隔的数据格式。
附录: XPath函数
1 XPath函数
XPath提供了函数库以便计算表达式。函数分为节点函数、字符串函数、布尔函数和数值函数。这里将介绍XPath必然支持的几个函数。关于参数和返回值请参见参考文献[5]。
2 节点集合函数
last
返回上下文的长度。
position
返回上下文的当前位置。
count
返回节点数目。
3 文字列関数
string
将对象转换成字符串。
concat
连接字符串。
start-with
第一个参数指定的字符串以第二个参数指定的字符串开始的话则返回真。
contains
第一个参数指定的字符串中包含第二个参数指定的字符串的话则返回真。
substring-before
如果在第一个参数指定的字符串中发现了第二个参数指定的字符串,那么返回第一个参数中首次发现第二个参数的位置之前的部分。
substring-after
如果在第一个参数指定的字符串中发现了第二个参数指定的字符串,那么返回第一个参数中首次发现第二个参数的位置之后的部分。
4 布尔函数
boolean
将对象转换成布尔值。
not
将真假值反转。
true
返回真。
false
返回假。
5 数值函数
number
将对象转换成数值。
sum
将参数指定的节点集合内的各个节点的字符串之转换成数值,然后返回它们的和。
round
返回与参数指定的值最接近的整数