用XSLT将XML从无层次格式转换为有层次格式或树状 
2008/6/2 21:30:20
阅读全文(2401) | 回复(1) | 编辑 | 精华
本文系Qr原创&答疑,更多内容请浏览Qr's blog,http://Qr.blogger.org.cn。 ****************************************以下为提问者的要求和代码:****************************************XML:<?xml version="1.0" encoding="gb2312"?><tree_tests> <tree_test ID="1" PARENT_ID="0" NAME="ROOT" SIZE="0" CHILDFLAG="T"/> <tree_test ID="2" PARENT_ID="1" NAME="A" SIZE="13" CHILDFLAG="T"/> <tree_test ID="3" PARENT_ID="1" NAME="B" SIZE="23" CHILDFLAG="T"/> <tree_test ID="4" PARENT_ID="1" NAME="C" SIZE="1" CHILDFLAG="T"/> <tree_test ID="5" PARENT_ID="2" NAME="A.A1" SIZE="34" CHILDFLAG="T"/> <tree_test ID="6" PARENT_ID="2" NAME="A.A2" SIZE="6" CHILDFLAG="F"/> <tree_test ID="7" PARENT_ID="5" NAME="A.A1.1" SIZE="7" CHILDFLAG="T"/> <tree_test ID="8" PARENT_ID="5" NAME="A.A1.2" SIZE="5" CHILDFLAG="T"/> <tree_test ID="9" PARENT_ID="2" NAME="A.A3" SIZE="3" CHILDFLAG="F"/> <tree_test ID="10" PARENT_ID="3" NAME="B.B1" SIZE="5" CHILDFLAG="T"/> <tree_test ID="11" PARENT_ID="4" NAME="C.C1" SIZE="6" CHILDFLAG="F"/> <tree_test ID="12" PARENT_ID="10" NAME="B.B1.1" SIZE="6" CHILDFLAG="F"/> <tree_test ID="13" PARENT_ID="7" NAME="A.A1.1.a" SIZE="12" CHILDFLAG="F"/></tree_tests> RESULT:<tree_test ID="1" NAME="ROOT"> <tree_test ID="2" NAME="A"> <tree_test ID="5" NAME="A.A1"> <tree_test ID="7" NAME="A.A1.1"> <tree_test ID="13" NAME="A.A1.1.a"> <tree_test ID="8" NAME="A.A1.2"> <tree_test ID="6" NAME="A.A2"> <tree_test ID="9" NAME="A.A3"> <tree_test ID="3" NAME="B"> <tree_test ID="10" NAME="B.B1"> <tree_test ID="12" NAME="B.B1.1"> <tree_test ID="4" NAME="C"> <tree_test ID="11" NAME="C.C1"> </tree_test> 附带有问题的XLS:<?xml version="1.0" encoding="gb2312" ?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><xsl:template match="/"> <xsl:call-template name="treetemplate"> <xsl:with-param name="parent_id" select="0"/> </xsl:call-template></xsl:template> <xsl:template name="treetemplate"> <xsl:param name="parent_id"/> <xsl:for-each select="/tree_tests/tree_test[@PARENT_ID=$parent_id]"> <xsl:value-of select="." /><!--不显示,因为无值--> <br/> <xsl:value-of select="$parent_id"/> <br/> Id:<xsl:value-of select="./@ID"/> <br/> PARENT_ID:<xsl:value-of select="./@PARENT_ID"/><br/> NAME:<xsl:value-of select="./@NAME"/><br/> SIZE:<xsl:value-of select="./@SIZE"/><br/> CHILDFLAG:<xsl:value-of select="./@CHILDFLAG"/> <br/> </xsl:for-each> <xsl:if test="CHILDFLAG='T'"> <xsl:call-template name="treetemplate"> <xsl:with-param name="parent_id" select="./@ID"/> </xsl:call-template> </xsl:if></xsl:template></xsl:stylesheet> ****************************************以下为Qr本人解题的思路和实现:****************************************思路:解决问题的关键在于两个 ID,即 ID 和 PARENT_ID。对于两个 tree_test,如果前者的 ID 等于后者的 PARENT_ID,说明后者是前者的子节点,而前者的 ID 就是其下级节点的 PARENT_ID。所以,我们可以以 PARENT_ID 为关键索引,取得当前节点的 ID 值,同时取得其下级节点的 PARENT_ID,根据这个 PARENT_ID,又可以取得其子节点的 ID 和 PARENT_ID,如此循环,直到所有节点的 ID 和 PARENT_ID 都遍历一次。如果把这个思路形成模板,就需要传参,在 XSL 中,要传参就要用命名模板。理论都是很抽象的,看不懂没关系,看代码。以下提供两种方法,思路都一样,只是实现的方法有些不同。 因为要用到命名模板,贴代码之前,先解析一下命名模板的调用:<xsl:call-template name="treetemplate"> <xsl:with-param name="parent_id" select="0"/></xsl:call-template> <xsl:call-template>元素的作用就是调用一个模板(template),name 指定被调用的模板的名称;<xsl:with-param>元素的作用就是向模板传递参数,name 指定参数名称,select 则定义了参数的值。 接收参数的方法就是在命名模板中使用:<xsl:param name="parent_id"/><xsl:param>元素被用来声明一个全局的或局部的参数。name 为参数名称,参数的引用使用“$”,如 $parent_id。 实现:下面是两个方法不同的命名模板,都能实现本人的解题思路。 模板1(采用current()函数的命名模板):<xsl:template name="treetemplate"> <xsl:param name="parent_id"/> <xsl:for-each select="//tree_test[@PARENT_ID=$parent_id]"> PARENT_ID:[<xsl:value-of select="@PARENT_ID"/>]-ID:[<xsl:value-of select="@ID"/>]-NAME:[<xsl:value-of select="@NAME"/>]<br/> <xsl:for-each select="//tree_test[@PARENT_ID=current()/@ID]"> PARENT_ID:[<xsl:value-of select="@PARENT_ID"/>]-ID:[<xsl:value-of select="@ID"/>]-NAME:[<xsl:value-of select="@NAME"/>]<br/> <xsl:call-template name="treetemplate"> <xsl:with-param name="parent_id" select="@ID"/> </xsl:call-template> </xsl:for-each> </xsl:for-each></xsl:template> 模板2(采用key()函数的命名模板):<xsl:template name="treetemplate"> <xsl:param name="parent_id"/> <xsl:for-each select="key('temp',$parent_id)"> <xsl:value-of select="@NAME"/><br/> <xsl:for-each select="key('temp',@ID)"> <xsl:value-of select="@NAME"/><br/> <xsl:call-template name="treetemplate"> <xsl:with-param name="parent_id" select="@ID"/> </xsl:call-template> </xsl:for-each> </xsl:for-each></xsl:template> 注意,key函数一般和 <xsl:key> 元素配合使用。采用模板2的方法,就必须加上以下这句代码:<xsl:key name="temp" match="tree_test" use="@PARENT_ID"/><xsl:key> 元素是个 top-level 元素,所以,必须加在 <xsl:template match="/"> 之前。 其实,这两个模板的关键就是current()和key()函数应用,这两个函数非常让人伤脑筋,要好好理解和体会,只要弄懂了,两个 treetemplate 模板就非常容易理解了。另外,XML 中 CHILDFLAG 的作用从提问者的 XSL 中可以看出,是标记当前节点是否存在子节点,但本人的代码没有考虑,因为每遍历一个节点,就判断一次 CHILDFLAG,没有必要,<xsl:for-each> 元素中的 XPath 的谓词断言表达式已经解决了这个问题。 以下给出两个命名模板执行的结果: 模板1对应的结果:PARENT_ID:[0]-ID:[1]-NAME:[ROOT]PARENT_ID:[1]-ID:[2]-NAME:[A]PARENT_ID:[2]-ID:[5]-NAME:[A.A1]PARENT_ID:[5]-ID:[7]-NAME:[A.A1.1]PARENT_ID:[7]-ID:[13]-NAME:[A.A1.1.a]PARENT_ID:[5]-ID:[8]-NAME:[A.A1.2]PARENT_ID:[2]-ID:[6]-NAME:[A.A2]PARENT_ID:[2]-ID:[9]-NAME:[A.A3]PARENT_ID:[1]-ID:[3]-NAME:[B]PARENT_ID:[3]-ID:[10]-NAME:[B.B1]PARENT_ID:[10]-ID:[12]-NAME:[B.B1.1]PARENT_ID:[1]-ID:[4]-NAME:[C]PARENT_ID:[4]-ID:[11]-NAME:[C.C1] 模板2对应的结果:ROOTAA.A1A.A1.1A.A1.1.aA.A1.2A.A2A.A3BB.B1B.B1.1CC.C1 这两个结果都不是问题想要的最终结果,实际上只是格式上的差别而已。只要进行简单的转换就可以,但是不能什么都包办,提问者总得思考一下吧,呵呵。 本文系Qr原创&答疑,更多内容请浏览Qr's blog,http://Qr.blogger.org.cn。
Posted by Qr on 2008/6/2 21:30:20
回复:用XSLT将XML从无层次格式转换为有层次格式或树状
2008/6/5 18:45:41
个人主页 | 引用回复 | 主人回复 | 返回 | 编辑 | 删除
这些对我怎么就是天方夜谭呢。 以下为blog主人的回复: 呵呵,尺有所短,寸有所长
Posted by 烟雨朦胧 on 2008/6/5 18:45:41
发表评论: |
|