XSLT用来解析XML文档并按照规定的样式输出数据。在XSLT中,我们通常使用for-each元素来遍历XML中的循环节点并输出内容,for-each元素允许你对要遍历的节点进行排序,参考文章“xslt中的for-each排序”。可是,如何在使用for-each元素时对要遍历的节点进行distinct操作以消除重复节点呢?先看下面的XML片段:
<
addresses
>
<
address
>
<
state
>FL
</
state
>
</
address
>
<
address
>
<
state
>GA
</
state
>
</
address
>
<
address
>
<
state
>MN
</
state
>
</
address
>
<
address
>
<
state
>FL
</
state
>
</
address
>
</
addresses
>
如何编写XSLT代码让其输出为下面的内容?
<
states
>
<
state
>FL
</
state
>
<
state
>GA
</
state
>
<
state
>MN
</
state
>
</
states
>
注意,上面的XML片段中,节点<address/>为重复节点,并且子节点<state/>存在重复的值,在输出的内容中将去掉这些具有重复节点。我们可以定义一个Key元素:
<
xsl:key
name
=”distinctState”
match
=”addresses/address”
use
=”./state”></xsl:key>
Key元素必须定义在元素xsl:template的外面,与元素xsl:template平级。在上面的key元素中,我们将key应用到addresses/address节点上,并规定该key的表达式为节点state的值。函数generate-id()用于返回唯一标识指定节点的字符串值。然后,我们在for-each元素中这样使用:
<
xsl:key
name
="distinctState"
match
="addresses/address"
use
="./state"
></
xsl:key
>
<
xsl:template
match
="/"
>
<
states
>
<
xsl:for-each
select
="addresses/address[generate-id() = generate-id(key('distinctState', ./state))]"
>
<
state
>
<
xsl:value-of
select
="./state"
></
xsl:value-of
>
</
state
>
</
xsl:for-each
>
</
states
>
</
xsl:template>
key元素的表达式中也可以使用函数来进行更加精确的匹配,如:
<
xsl:key
name
="distinctState"
match
="/Customers/Customer"
use
="substring(Address, string-length(Address)-1)"
></
xsl:key
>
<
xsl:template
match
="/"
>
<
xsl:for-each
select
="Customers/Customer[generate-id() = generate-id(key('distinctState', substring(Address, (string-length(Address)-1))))]"
>
<
xsl:call-template
name
="AggregateForState"
>
<
xsl:with-param
name
="state"
select
="substring(Address, (string-length(Address)-1))"
/>
</
xsl:call-template
>
</
xsl:for-each
>
</
xsl:template
>
在看一个复杂点的例子,对XML元素进行分组输出:
<
items
>
<
item
>
<
name
>name1
</
name
>
<
group
>group1
</
group
>
</
item
>
<
item
>
<
name
>name2
</
name
>
<
group
>group1
</
group
>
</
item
>
<
item
>
<
name
>name3
</
name
>
<
group
>group2
</
group
>
</
item
>
<
item
>
<
name
>name4
</
name
>
<
group
>group2
</
group
>
</
item
>
<
item
>
<
name
>name5
</
name
>
<
group
>group2
</
group
>
</
item
>
<
item
>
<
name
>name6
</
name
>
<
group
>group1
</
group
>
</
item
>
<
item
>
<
name
>name7
</
name
>
<
group
>group3
</
group
>
</
item
>
<
item
>
<
name
>name8
</
name
>
<
group
>group3
</
group
>
</
item
>
<
item
>
<
name
>name9
</
name
>
<
group
>group4
</
group
>
</
item
>
<
item
>
<
name
>name10
</
name
>
<
group
>group1
</
group
>
</
item
>
</
items
>
我们希望编写XSLT将上面的XML解析成下面的样子:
完整的XSLT代码如下:
<?
xml version="1.0" encoding="utf-8"
?>
<
xsl:stylesheet
version
="1.0"
xmlns:xsl
="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl
="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes
="msxsl"
>
<
xsl:output
method
="html"
indent
="yes"
omit-xml-declaration
="yes"
/>
<
xsl:key
name
="distinctState"
match
="items/item"
use
="./group"
/>
<
xsl:template
match
="/"
>
<
xsl:variable
name
="tabStr"
>
<
xsl:for-each
select
="items/item[generate-id()=generate-id(key('distinctState', ./group))]"
>
<
xsl:value-of
select
="./group"
/>
<
xsl:if
test
="position()!=last()"
>|
</
xsl:if
>
</
xsl:for-each
>
</
xsl:variable
>
tabStr:
<
xsl:value-of
select
="$tabStr"
/>
<
br
/>
<
br
/>
<
xsl:for-each
select
="items/item[generate-id()=generate-id(key('distinctState', ./group))]"
>
<
xsl:variable
name
="tabName"
>
<
xsl:call-template
name
="output-tokens"
>
<
xsl:with-param
name
="list"
select
="$tabStr"
/>
<
xsl:with-param
name
="separator"
>|
</
xsl:with-param
>
<
xsl:with-param
name
="pos"
select
="position()"
/>
</
xsl:call-template
>
</
xsl:variable
>
<
xsl:value-of
select
="$tabName"
/>
<
br
/>
<
hr
/>
<
xsl:for-each
select
="//items/item"
>
<
xsl:if
test
="./group = $tabName"
>
<
xsl:value-of
select
="name"
/>
<
br
/>
</
xsl:if
>
</
xsl:for-each
>
<
br
/>
</
xsl:for-each
>
</
xsl:template
>
<
xsl:template
name
="output-tokens"
>
<
xsl:param
name
="list"
/>
<
xsl:param
name
="separator"
/>
<
xsl:param
name
="pos"
/>
<
xsl:variable
name
="newlist"
select
="concat(normalize-space($list), $separator)"
/>
<
xsl:variable
name
="first"
select
="substring-before($newlist, $separator)"
/>
<
xsl:variable
name
="remaining"
select
="substring-after($newlist, $separator)"
/>
<
xsl:choose
>
<
xsl:when
test
="$pos = 1"
>
<
xsl:value-of
select
="$first"
/>
</
xsl:when
>
<
xsl:otherwise
>
<
xsl:call-template
name
="output-tokens"
>
<
xsl:with-param
name
="list"
select
="$remaining"
/>
<
xsl:with-param
name
="separator"
select
="$separator"
/>
<
xsl:with-param
name
="pos"
select
="$pos - 1"
/>
</
xsl:call-template
>
</
xsl:otherwise
>
</
xsl:choose
>
</
xsl:template
>
</
xsl:stylesheet
>
在上面的代码中,我们首先定义了元素key,并应用到节点items/item上,表达式为“./group”。变量tabStr用来存放以字符“|”分隔的group节点的值,并使用了distinct操作。接下来我们在页面上打印了变量tabStr的值。紧接着的for-each元素则将整个XML文档按照group分组进行输出。注意自定义的template “output-tokens”,在其中使用了一点技巧用来按不同的分组找出对应的分组名称,类似于C#中将字符串使用Split函数存放到数组中。有关output-tokens模板的技巧可以参考我的另一篇文章“在xslt中实现split方法对查询字符串进行分隔”。
在XSLT的使用中有许多的技巧,灵活掌握这些技巧可以大大缩短我们的开发时间。