在CSDN上无意中看到了这样一篇老帖 ,关于XSLT转换的。
XSLT原文:
<?xml version="1.0" encoding="GB2312"?>
<?xml-stylesheet type="text/xsl" href="sort.xsl"?>
<root>
<item f1="浙江" f2="绍兴" f3="aaa"/>
<item f1="江苏" f2="苏州" f3="bbb"/>
<item f1="浙江" f2="杭州" f3="ccc"/>
<item f1="山东" f2="济南" f3="ddd"/>
<item f1="山东" f2="青岛" f3="eee"/>
<item f1="江苏" f2="南京" f3="fff"/>
<item f1="山东" f2="青岛" f3="ggg"/>
<item f1="江苏" f2="南京" f3="hhh"/>
<item f1="山东" f2="济南" f3="iii"/>
</root>
<?xml-stylesheet type="text/xsl" href="sort.xsl"?>
<root>
<item f1="浙江" f2="绍兴" f3="aaa"/>
<item f1="江苏" f2="苏州" f3="bbb"/>
<item f1="浙江" f2="杭州" f3="ccc"/>
<item f1="山东" f2="济南" f3="ddd"/>
<item f1="山东" f2="青岛" f3="eee"/>
<item f1="江苏" f2="南京" f3="fff"/>
<item f1="山东" f2="青岛" f3="ggg"/>
<item f1="江苏" f2="南京" f3="hhh"/>
<item f1="山东" f2="济南" f3="iii"/>
</root>
要转换的结果文件:
江苏- 南京:fff,hhh 苏州:bbb
山东- 济南:ddd,iii 青岛:eee,ggg
浙江- 杭州:ccc 绍兴:aaa
山东- 济南:ddd,iii 青岛:eee,ggg
浙江- 杭州:ccc 绍兴:aaa
意思是要把在同一个省同一个市的归纳在一起,并且省市都要按字典排序。
楼主想到了一点:轴是用在原文档中的。
其实,这个问题可以解决:
首先第一步:提取f1
代码:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="root/item">
<xsl:sort select="@f1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(preceding-sibling::*[@f1=current()/@f1])">
<xsl:value-of select="concat(@f1,'-')"/>
<br/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
OK,
江苏-
山东-
浙江-
山东-
浙江-
现在需要输出第二项内容:即f2,可是该如何实现呢?我们知道apply-templates,通常只应用于子孙节点的模板。第一步我们已经到了item了,下面就是属性了,应用属性的模板,该如何实现属性的唯一性输出呢?
其实打破常理来想,我们可以再次应用item的模板,但是如何应用呢?我们应该先利用for-each将当前节点移到item的顶层,那么就可以再次应用item的模板了,但是,另一个问题又出来了,再次应用这个模板,是不是会混乱了,我们这次的目的是输出f2啊,OK,还记得吗?<xsl:templates>有一个mode属性。代码也就顺理成章的出来了(当然还得保证所选定的item节点没变):
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="root/item" mode="a" >
<xsl:sort select="@f1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item" mode="a">
<xsl:if test="not(preceding-sibling::*[@f1=current()/@f1])">
<xsl:value-of select="concat(@f1,'-')"/>
<xsl:variable name="f1" select="@f1"/>
<xsl:for-each select="..">
<xsl:apply-templates select="item[@f1 = $f1]" mode="b">
<xsl:sort select="@f2"/>
</xsl:apply-templates>
</xsl:for-each>
<br/>
</xsl:if>
</xsl:template>
<xsl:template match="item" mode="b">
<xsl:if test="not(preceding-sibling::*[@f2=current()/@f2])">
<xsl:value-of select="concat(' ',@f2,':')"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
本文出自 51CTO.COM技术博客f3
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="root/item" mode="a" >
<xsl:sort select="@f1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item" mode="a">
<xsl:if test="not(preceding-sibling::*[@f1=current()/@f1])">
<xsl:value-of select="concat(@f1,'-')"/>
<xsl:variable name="f1" select="@f1"/>
<xsl:for-each select="..">
<xsl:apply-templates select="item[@f1 = $f1]" mode="b">
<xsl:sort select="@f2"/>
</xsl:apply-templates>
</xsl:for-each>
<br/>
</xsl:if>
</xsl:template>
<xsl:template match="item" mode="b">
<xsl:if test="not(preceding-sibling::*[@f2=current()/@f2])">
<xsl:value-of select="concat(' ',@f2,':')"/>
<xsl:variable name="f2" select="@f2"/>
<xsl:for-each select="..">
<xsl:apply-templates select="item[@f2 = $f2]" mode="c">
<xsl:sort select="@f3"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="item" mode="c">
<xsl:if test="not(preceding-sibling::*[@f3=current()/@f3])">
<xsl:if test="position()!=1">
<xsl:value-of select="','"/>
</xsl:if>
<xsl:value-of select="@f3"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
得到最终的输出:
江苏- 南京:fff,hhh 苏州:bbb
山东- 济南:ddd,iii 青岛:eee,ggg
浙江- 杭州:ccc 绍兴:aaa
山东- 济南:ddd,iii 青岛:eee,ggg
浙江- 杭州:ccc 绍兴:aaa
OK,总算得到最终的结果了,但是是否完美了呢?或者说程序没有问题呢?
有问题,而且问题还很大。
正如原来问题的楼主所说:preceding-sibling是指在上下文节点在原XML文档中的前驱节点集合。所以在mode b中的代码:
<xsl:if test="not(preceding-sibling::*[@f2=current()/@f2])">
是判断在此之前没有出现过相同的f2,用preceding-sibling没错,但是这里有一个前提条件:同一个f1,在原文件中如果当前节点存在一个相同的f2,但不是同一个f1,那么当前节点的f2将无法输出了。
如果说f2(代表的是城市)很难有同名的,但是mode c呢,
<xsl:if test="not(preceding-sibling::*[@f3=current()/@f3])">
f3同名的多了吧,更何况,有没有同名的城市还很难说呢。
总之为了程序的严密性,我们必须将其改成:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="root/item" mode="a" >
<xsl:sort select="@f1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item" mode="a">
<xsl:if test="not(preceding-sibling::*[@f1=current()/@f1])">
<xsl:value-of select="concat(@f1,'-')"/>
<xsl:variable name="f1" select="@f1"/>
<xsl:for-each select="..">
<xsl:apply-templates select="item[@f1 = $f1]" mode="b">
<xsl:sort select="@f2"/>
</xsl:apply-templates>
</xsl:for-each>
<br/>
</xsl:if>
</xsl:template>
<xsl:template match="item" mode="b">
<xsl:if test="not(preceding-sibling::*[@f1=current()/@f1][@f2=current()/@f2])">
<xsl:value-of select="concat(' ',@f2,':')"/>
<xsl:variable name="f2" select="@f2"/>
<xsl:for-each select="..">
<xsl:apply-templates select="item[@f2 = $f2]" mode="c">
<xsl:sort select="@f3"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="item" mode="c">
<xsl:if test="not(preceding-sibling::*[@f1=current()/@f1][@f2=current()/@f2][@f3=current()/@f3])">
<xsl:if test="position()!=1">
<xsl:value-of select="','"/>
</xsl:if>
<xsl:value-of select="@f3"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XML