使用XSLT基于属性值的Flat to Nested结构

时间:2012-06-20 05:09:43

标签: xslt-2.0

我有一个扁平的结构化XML文件,如下所示:

<rs>
    <r id="r1" lev="0"/>
    <r id="r2" lev="1"/>
    <r id="r3" lev="0"/>
    <r id="r4" lev="1"/>
    <r id="r5" lev="2"/>
    <r id="r6" lev="3"/>
    <r id="r7" lev="0"/>
    <r id="r8" lev="1"/>
    <r id="r9" lev="2"/>
</rs>

我需要转换为嵌套的。规则是这样的,所有r[number(@lev) gt 0]都应该嵌套在r[number(@lev) eq 0]中。输出将是这样的:

<rs>
    <r id="r1">
        <r id="r2"/>
    </r>
    <r id="r3">
        <r id="r4">
            <r id="r5">
                <r id="r6"/>
            </r>
        </r>
    </r>
    <r id="r7">
        <r id="r8">
            <r id="r9"/>
        </r>
    </r>
</rs>

我尝试的是以下转型:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:output indent="yes"/>

    <xsl:template match="/">
        <rs>
            <xsl:apply-templates select="node()|@*"/>
        </rs>
    </xsl:template>

    <xsl:template match="r">
        <xsl:variable name="lev" select="number(@lev)" as="xs:double"/>
        <r>
            <xsl:copy-of select="@id"/>
            <xsl:apply-templates select="following-sibling::r[not(number(@lev) eq $lev)
                                         and 
                                         count(preceding-sibling::r[number(@lev) eq $lev]) eq 1]"/>
        </r>
    </xsl:template>

</xsl:stylesheet>

但是,这并没有给我预期的结果。指出我的编码错误或任何其他方法来完成工作,非常感谢。

3 个答案:

答案 0 :(得分:3)

此转化

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kRByLevelAndParent" match="r"
  use="concat(generate-id(preceding-sibling::r
                            [not(@lev >= current()/@lev)][1]),
                          @lev
                          )"/>

 <xsl:template match="/*">
  <rs>
    <xsl:apply-templates select="key('kRByLevelAndParent', '0')"/>
  </rs>
 </xsl:template>

 <xsl:template match="r">
  <r id="{@id}">
    <xsl:apply-templates select=
    "key('kRByLevelAndParent',
         concat(generate-id(), @lev+1)
         )"/>
  </r>
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档时:

<rs>
    <r id="r1" lev="0"/>
    <r id="r2" lev="1"/>
    <r id="r3" lev="0"/>
    <r id="r4" lev="1"/>
    <r id="r5" lev="2"/>
    <r id="r6" lev="3"/>
    <r id="r7" lev="0"/>
    <r id="r8" lev="1"/>
    <r id="r9" lev="2"/>
</rs>

会产生想要的正确结果:

<rs>
   <r id="r1">
      <r id="r2"/>
   </r>
   <r id="r3">
      <r id="r4">
         <r id="r5">
            <r id="r6"/>
         </r>
      </r>
   </r>
   <r id="r7">
      <r id="r8">
         <r id="r9"/>
      </r>
   </r>
</rs>

<强>解释

使用复合键进行位置分组 - 对于其所有“子”,元素是第一个前一个兄弟,因此其lev属性小于其各自的lev属性。

答案 1 :(得分:1)

除非另有要求,否则Dimitre倾向于使用XSLT 1.0回答问题。这可能是一个正确的猜测,但我认为值得指出的是,XSLT 2.0现在可以广泛使用和使用,并且在XSLT 2.0中分组问题的代码要简单得多(它可能并不总是更短,但它是更具可读性)。与Dimitre不同,我没有时间或倾向于为每个问题提供完美且经过测试的完美解决方案,但是如果你想看到这个问题的XSLT 2.0解决方案,我在几年前写的一篇论文中有一篇:< / p>

http://www.saxonica.com/papers/ideadb-1.1/mhk-paper.xml

搜索递归模板名称=“进程级别”。

答案 2 :(得分:0)

由于我需要在临时变量中应用转换,因此使用xsl:key无济于事。如果我必须使用 Dimitre的解决方案 ,我必须更改现有代码。

显然,在我的问题中,我没有在这方面描述太多,这是我的错误。

来自 博士提供的//programlisting[contains(.,'xsl:template name="process-level"')]链接。凯 我已经结束了解决方案,可能是其他人可能会在以后使用它:

样式表

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs">
    <xsl:output indent="yes"/>

    <xsl:template match="/*">
        <rs>
            <xsl:call-template name="process-level">
                <xsl:with-param name="context" 
                    select="r"/>
                <xsl:with-param name="level" 
                    select="0"/>
            </xsl:call-template>
        </rs>
    </xsl:template>

    <xsl:template name="process-level">
        <xsl:param name="context" required="yes" as="element()*"/>
        <xsl:param name="level" as="xs:double"/>
        <xsl:for-each-group select="$context"
            group-starting-with="*[number(@lev) eq $level]">
            <xsl:element name="{name()}">
                <!--<xsl:variable name="position" as="xs:double">
                    <xsl:number level="any" count="*[starts-with(local-name(), 'r')]"/>
                </xsl:variable>-->
                <xsl:copy-of select="@id"/>
                <xsl:call-template name="process-level">
                    <xsl:with-param name="context" select="current-group()[position() != 1]"/>
                    <xsl:with-param name="level" select="$level + 1"/>
                </xsl:call-template>
            </xsl:element>
        </xsl:for-each-group>
    </xsl:template>

</xsl:stylesheet>

输入XML

<rs>
    <r id="r1" lev="0"/>
    <r id="r2" lev="1"/>
    <r id="r3" lev="0"/>
    <r id="r4" lev="1"/>
    <r id="r5" lev="2"/>
    <r id="r6" lev="3"/>
    <r id="r7" lev="0"/>
    <r id="r8" lev="1"/>
    <r id="r9" lev="2"/>
</rs>

结果

<rs>
   <r id="r1">
      <r id="r2"/>
   </r>
   <r id="r3">
      <r id="r4">
         <r id="r5">
            <r id="r6"/>
         </r>
      </r>
   </r>
   <r id="r7">
      <r id="r8">
         <r id="r9"/>
      </r>
   </r>
</rs>