0

Yes, I know some of the answers, e.g. Flat to Nested structure based on attribute value using XSLT but there is a difference that matters to me.

Given is the following flat XML structure. The level information may contain gaps. There can be any number of elements without level information between the elements containing level information. Such elements should be assigned to the previous element with level specification.

<doc>
  <item>
    <meta>
        <text>abc</text>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>1</level>
        <title>a</title>
      </para>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>2</level>
        <title>b</title>
      </para>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>1</level>
        <title>c</title>
      </para>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>2</level>
        <title>d</title>
      </para>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>2</level>
        <title>e</title>
      </para>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>4</level>
        <title>f</title>
      </para>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>1</level>
        <title>g</title>
      </para>
    </meta>
  </item>
  <item>
    <meta>
        <text>def</text>
    </meta>
  </item>
  <item>
    <meta>
        <text>ghi</text>
    </meta>
  </item>
  <item>
    <meta>
      <para>
        <level>2</level>
        <title>h</title>
      </para>
    </meta>
  </item>
</doc>

The result after the transformation should look like this ...

<text>abc</text>
<test title="a">
   <test title="b"/>
</test>
<test para="c">
   <test title="d"/>
   <test title="e">
      <test para="f"/>
   </test>
</test>
<test title="g">
    <text>def</text>
    <text>ghi</text>
   <test title="h"/>
</test>

My approach to the transformation is as follows.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="child" match="item"
    use="generate-id(preceding-sibling::item[(meta/para/level) &lt;= ((current()/meta/para/level) - 1)][1])"/>

    <xsl:template match="/doc">
        <xsl:apply-templates select="item[(meta/para/level) = 1]"/>
    </xsl:template>

    <xsl:template match="item">
        <test title="{meta/para/title}">
            <xsl:apply-templates select="key('child', generate-id())"/>
        </test>
    </xsl:template>

</xsl:stylesheet>

Unfortunately, I can not find the solution.

Denis Giffeler
  • 1,499
  • 12
  • 21

1 Answers1

1

How about:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="child1" 
         match="item[meta/para/level]"
         use="generate-id(preceding-sibling::item[meta/para/level &lt; current()/meta/para/level][1])"/>

<xsl:key name="child2" 
         match="item[meta/text]"
         use="generate-id(preceding-sibling::item[meta/para/level][1])"/>

<xsl:template match="/doc">
    <root>
        <xsl:apply-templates select="item[meta/para/level = 1] | key('child2', '')"/>
    </root>
</xsl:template>

<xsl:template match="item[meta/para/title]">
    <test title="{meta/para/title}">
        <xsl:apply-templates select="key('child1', generate-id()) | key('child2', generate-id())"/>
    </test>
</xsl:template>

<xsl:template match="item[meta/text]">
    <text>
        <xsl:value-of select="meta/text"/>
    </text>
</xsl:template>

</xsl:stylesheet>

I have added a root element to make the result a well-formed XML.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51