5

我有一个平面结构的 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>

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

4

3 回答 3

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属性。

于 2012-06-20T05:32:36.040 回答
2

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

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

搜索递归模板名称="process-level"。

于 2012-06-20T08:10:14.833 回答
0

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

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

Dr. Kay//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>
于 2012-06-20T10:24:22.270 回答