以前很讨厌轴这个概念,但最近却用它解决了几个问题,认识上也有了提高,记录一下。
原XML文档:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<customer>
<name>Neo</name>
<amount>20</amount>
<description>amount using calculation 1</description>
</customer>
<customer>
<name>Neo</name>
<amount>30</amount>
<description>amount using calculation 2</description>
</customer>
<customer>
<name>Mark</name>
<amount>40</amount>
<description>amount using calculation 3</description>
</customer>
<customer>
<name>Neo</name>
<amount>50</amount>
<description>amount using calculation 4</description>
</customer>
<customer>
<name>Mark</name>
<amount>60</amount>
<description>amount using calculation 5</description>
</customer>
<customer>
<name>Meddy</name>
<amount>70</amount>
<description>amount using calculation 6</description>
</customer>
</root>
在root的根目录下有多个 customer节点,每个customer节点有name,amout,description三个子节点,现在的问题是想输出一个HTML表格,相同的 name 只显示一次,不同的amount和description显示在 name下面。
也就是
Nero
20 amount using calculation 1
30 amount using calculation 2
50 amount using calculation 4
mark
...............
初始的想法是,遍历每个节点,找到所有不同的name,然后对每个name,执行一次输出操作。
但是后来发现XSLT没有数组,即使用字符串,也不太好处理。最终使用preceding轴解决了这个问题:
那就是在第一次出现一个name的时候,执行一次输出操作。
具体代码:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="GB2312" indent="yes"/>
<xsl:template match="/">
<html>
<head>
<title>Result</title>
</head>
<body>
<table>
<xsl:for-each select="root/customer">
<xsl:if test="not(preceding-sibling::customer[name=current()/name])">
<!--前驱轴上不存在name跟当前节点的name相同的customer节点。-->
<tr>
<xsl:apply-templates select="name"/>
</tr>
<xsl:for-each select="../customer[name=current()/name]">
<tr>
<xsl:apply-templates select="amount"/>
<xsl:apply-templates select="description"/>
</tr>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="amount|description|name">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
呵呵,解决了问题,后来想到,让输出结果,按name的升序排列,加一个排序规则即可。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="GB2312" indent="yes"/>
<xsl:template match="/">
<html>
<head>
<title>Result</title>
</head>
<body>
<table>
<xsl:for-each select="root/customer">
<xsl:sort select="name"/>
<xsl:if test="not(preceding-sibling::customer[name=current()/name])">
<tr>
<xsl:apply-templates select="name"/>
</tr>
<xsl:for-each select="../customer[name=current()/name]">
<tr>
<xsl:apply-templates select="amount"/>
<xsl:apply-templates select="description"/>
</tr>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="amount|description|name">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
顺便理解了sort的用法,原来sort只要加在循环或者模板中,就可以把所选定的节点集合看成已经排好序的了。
另外一个问题:
XML原文件:
<?xml version="1.0"?>
<list>
<message>
<header>0001</header>
<lines>
<line>aaaa</line>
<line>bbbb</line>
<line>aaaa</line>
<line>ffff</line>
</lines>
<lines>
<line>aaaa</line>
<line>message1 group2 line2 </line>
<line>bbbb</line>
</lines>
</message>
<message>
<header>0002</header>
<lines>
<line>aaaa</line>
<line>message2 group1 line2 </line>
<line>aaaa</line>
<line>message2 group1 line4 </line>
</lines>
<lines>
<line>message2 group2 line1 </line>
<line>aaaa</line>
</lines>
<lines>
<line>message2 group3 line1 </line>
<line>aaaa</line>
<line>aaaa</line>
</lines>
</message>
<message>
<header>0003</header>
<lines>
<line>message3 group2 line1 </line>
<line>message2 group2 line2 </line>
</lines>
<lines>
<line>message3 group3 line1 </line>
<line>aaaa</line>
<line>aaaa</line>
</lines>
</message>
</list>
也就是说,一个list下有多个message,每个message有一个header和多个lines,每个lines下有多个line.现在的问题是,输出每个message中line为aaaa的节点序号,该序号是aaaa的line在message中出现的序号。输出结果像像下边所示:
0001:1 2 3
0002:1 2 3 4 5
0003:1 2
考虑用preceding轴,求每个aaaa的line前有多少这样的节点,但是,前面的message的line节点也会被记录在内,又想到每个line和另外message的line虽然名字相同,但是路径不同。
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="list/message">
<xsl:text>
</xsl:text>
<xsl:value-of select="concat(header,':')"/>
<xsl:for-each select="lines/line[. = 'aaaa']">
<xsl:value-of select="count(preceding::line[. = 'aaaa']) - count(preceding::message/lines/line[. = 'aaaa'])+1"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
问题解决。