-1

I have a flat xml file that needs to be converted to hierarchical. I used the nested grouping idea from here xsl:for-each-group help needed. It's working for the most part except for a couple of issues:

1) The elements root1 and root2 are not showing up.

2) The location of element level21 is incorrect. The first level21 with sequencecount=2 should be between the 2 level2 elements.

Any help is really appreciated.

Thanks.

Source XML

<root>
<root1>1234</root1>
<root2>5678</root2>
<level1>
    <a>line 1</a>
    <b>line 2</b>
    <c>line 3</c>
</level1>
<level2>
    <d>line 4</d>
    <e>line 5</e>
    <f>line 6</f>
    <g>line 7</g>
</level2>
<level3>
    <h>line 8</h>
    <i>line 9</i>
    <j>line 10</j>
    <k>line 11</k>
    <l>line 12</l>
</level3>
<level4>
    <m>line 13</m>
    <n>line 14</n>
    <o>line 15</o>
</level4>
<level4>
    <m>line 13</m>
    <n>line 14</n>
    <o>line 15</o>
</level4>
<level21>
    <d>line 214</d>
    <e>line 215</e>
    <f>line 216</f>
    <g>line 217</g>
</level21>
<level2>
    <d>line 19</d>
    <e>line 20</e>
    <f>line 21</f>
    <g>line 22</g>
</level2>
<level3>
    <h>line 23</h>
    <i>line 24</i>
    <j>line 25</j>
    <k>line 26</k>
    <l>line 27</l>
</level3>
<level4>
    <m>line 28</m>
    <n>line 29</n>
    <o>line 30</o>
</level4>
<level4>
    <m>line 13</m>
    <n>line 14</n>
    <o>line 15</o>
</level4>
<level21>
    <d>line 224</d>
    <e>line 225</e>
    <f>line 226</f>
    <g>line 227</g>
</level21>

Required Result XML

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <root1>1234</root1>
  <root2>5678</root2>
  <level1>
    <a>line 1</a>
    <b>line 2</b>
    <c>line 3</c>
    <level2>
      <d>line 4</d>
      <e>line 5</e>
      <f>line 6</f>
      <g>line 7</g>
      <level3>
        <SequenceCount>1</SequenceCount>
        <h>line 8</h>
        <i>line 9</i>
        <j>line 10</j>
        <k>line 11</k>
        <l>line 12</l>
        <level4>
          <SequenceCount>1</SequenceCount>
          <m>line 13</m>
          <n>line 14</n>
          <o>line 15</o>
        </level4>
        <level4>
          <SequenceCount>2</SequenceCount>
          <m>line 13</m>
          <n>line 14</n>
          <o>line 15</o>
        </level4>
      </level3>
    </level2>
    <level21>
          <SequenceCount>2</SequenceCount>
          <d>line 214</d>
          <e>line 215</e>
          <f>line 216</f>
          <g>line 217</g>
    </level21>
    <level2>
      <d>line 19</d>
      <e>line 20</e>
      <f>line 21</f>
      <g>line 22</g>
      <level3>
        <SequenceCount>3</SequenceCount>
        <h>line 23</h>
        <i>line 24</i>
        <j>line 25</j>
        <k>line 26</k>
        <l>line 27</l>
        <level4>
          <SequenceCount>1</SequenceCount>
          <m>line 28</m>
          <n>line 29</n>
          <o>line 30</o>
        </level4>
        <level4>
          <SequenceCount>2</SequenceCount>
          <m>line 13</m>
          <n>line 14</n>
          <o>line 15</o>
        </level4>
      </level3>
    </level2>    
    <level21>
      <SequenceCount>4</SequenceCount>
      <d>line 224</d>
      <e>line 225</e>
      <f>line 226</f>
      <g>line 227</g>
    </level21>
  </level1>
</root>

My XSL

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

    <xsl:output indent="yes" />

    <xsl:template match="/">
        <xsl:for-each-group select="*" group-starting-with="root">
            <root>
                <xsl:for-each-group select="*" group-starting-with="*[name(.)='level1']">
                    <xsl:choose>
                        <xsl:when test="name(.)='level1'">
                            <level1>
                                <xsl:apply-templates />
                                <xsl:for-each-group select="current-group()" group-starting-with="*[name(.)='level2']">
                                    <xsl:choose>
                                        <xsl:when test="name(.)='level2'">
                                            <level2>
                                                <xsl:apply-templates />
                                                <xsl:for-each-group select="current-group()" group-starting-with="*[name(.)='level3']">
                                                    <xsl:choose>
                                                        <xsl:when test="name(.)='level3'">
                                                            <level3>
                                                                <xsl:element name="SequenceCount"><xsl:number count="level3|level21" level="any"/></xsl:element>
                                                                <xsl:apply-templates />
                                                                <xsl:for-each-group select="current-group()" group-starting-with="*[name(.)='level4']">
                                                                    <xsl:choose>
                                                                        <xsl:when test="name(.)='level4'">
                                                                            <level4>
                                                                                <xsl:element name="SequenceCount"><xsl:number value="position() - 1"/></xsl:element>
                                                                                <xsl:apply-templates />
                                                                            </level4>
                                                                        </xsl:when>
                                                                    </xsl:choose>
                                                                </xsl:for-each-group>
                                                            </level3>
                                                        </xsl:when>
                                                    </xsl:choose>
                                                </xsl:for-each-group>
                                            </level2>
                                        </xsl:when>
                                    </xsl:choose>
                                </xsl:for-each-group>
                                <xsl:for-each-group select="current-group()" group-starting-with="*[name(.)='level21']">
                                    <xsl:choose>
                                        <xsl:when test="name(.)='level21'">
                                            <level21>
                                                <xsl:element name="SequenceCount"><xsl:number count="level3|level21" level="any"/></xsl:element>
                                                <xsl:apply-templates />
                                            </level21>
                                        </xsl:when>
                                    </xsl:choose>
                                </xsl:for-each-group>
                            </level1>
                        </xsl:when>
                    </xsl:choose>
                </xsl:for-each-group>
            </root>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="node()">
        <xsl:copy>
            <xsl:apply-templates select="node()" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Current Output XML

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <level1>
    <a>line 1</a>
    <b>line 2</b>
    <c>line 3</c>
    <level2>
      <d>line 4</d>
      <e>line 5</e>
      <f>line 6</f>
      <g>line 7</g>
      <level3>
        <SequenceCount>1</SequenceCount>
        <h>line 8</h>
        <i>line 9</i>
        <j>line 10</j>
        <k>line 11</k>
        <l>line 12</l>
        <level4>
          <SequenceCount>1</SequenceCount>
          <m>line 13</m>
          <n>line 14</n>
          <o>line 15</o>
        </level4>
        <level4>
          <SequenceCount>2</SequenceCount>
          <m>line 13</m>
          <n>line 14</n>
          <o>line 15</o>
        </level4>
      </level3>
    </level2>
    <level2>
      <d>line 19</d>
      <e>line 20</e>
      <f>line 21</f>
      <g>line 22</g>
      <level3>
        <SequenceCount>3</SequenceCount>
        <h>line 23</h>
        <i>line 24</i>
        <j>line 25</j>
        <k>line 26</k>
        <l>line 27</l>
        <level4>
          <SequenceCount>1</SequenceCount>
          <m>line 28</m>
          <n>line 29</n>
          <o>line 30</o>
        </level4>
        <level4>
          <SequenceCount>2</SequenceCount>
          <m>line 13</m>
          <n>line 14</n>
          <o>line 15</o>
        </level4>
      </level3>
    </level2>
    <level21>
      <SequenceCount>2</SequenceCount>
      <d>line 214</d>
      <e>line 215</e>
      <f>line 216</f>
      <g>line 217</g>
    </level21>
    <level21>
      <SequenceCount>4</SequenceCount>
      <d>line 224</d>
      <e>line 225</e>
      <f>line 226</f>
      <g>line 227</g>
    </level21>
  </level1>
</root>
Community
  • 1
  • 1

1 Answers1

0

I think the following stylesheet gives you the grouping you want:

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

<xsl:output indent="yes"/>

<xsl:function name="mf:group" as="node()*">
  <xsl:param name="elements" as="element()*"/>
  <xsl:param name="level" as="xs:integer"/>
  <xsl:for-each-group select="$elements" group-starting-with="*[starts-with(local-name(), concat('level', $level))]">
    <xsl:choose>
      <xsl:when test="self::*[starts-with(local-name(), concat('level', $level))]">
        <xsl:copy>
          <xsl:if test="$level = (3, 4)">
            <SequenceCount><xsl:value-of select="position()"/></SequenceCount>
          </xsl:if>
          <xsl:apply-templates/>
          <xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
        </xsl:copy>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="current-group()"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
</xsl:function>

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

<xsl:template match="root">
  <xsl:copy>
    <xsl:sequence select="mf:group(*, 1)"/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

For the SequenceCount element some values are as in your posted result sample, some are different, some are missing. Let's first establish whether the grouping is as you want it, then we can try to improve the sequence count value. You might want to explain in writing what that count refers to.

[edit]Based on your XSLT code I have added code to compute the SequenceNumber for the different levels, the complete stylesheet is now

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

<xsl:output indent="yes"/>

<xsl:function name="mf:group" as="node()*">
  <xsl:param name="elements" as="element()*"/>
  <xsl:param name="level" as="xs:integer"/>
  <xsl:for-each-group select="$elements" group-starting-with="*[starts-with(local-name(), concat('level', $level))]">
    <xsl:choose>
      <xsl:when test="self::*[starts-with(local-name(), concat('level', $level))]">
        <xsl:copy>
          <xsl:choose>
            <xsl:when test="self::level3">
              <SequenceCount><xsl:number count="level3 | level21"/></SequenceCount>
            </xsl:when>
            <xsl:when test="self::level4">
              <SequenceCount><xsl:value-of select="position()"/></SequenceCount>
            </xsl:when>
            <xsl:when test="self::level21">
              <SequenceCount><xsl:number count="level3|level21"/></SequenceCount>
            </xsl:when>
          </xsl:choose>
          <xsl:apply-templates/>
          <xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
        </xsl:copy>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="current-group()"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
</xsl:function>

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

<xsl:template match="root">
  <xsl:copy>
    <xsl:sequence select="mf:group(*, 1)"/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

With Saxon for the input

<root>
<root1>1234</root1>
<root2>5678</root2>
<level1>
    <a>line 1</a>
    <b>line 2</b>
    <c>line 3</c>
</level1>
<level2>
    <d>line 4</d>
    <e>line 5</e>
    <f>line 6</f>
    <g>line 7</g>
</level2>
<level3>
    <h>line 8</h>
    <i>line 9</i>
    <j>line 10</j>
    <k>line 11</k>
    <l>line 12</l>
</level3>
<level4>
    <m>line 13</m>
    <n>line 14</n>
    <o>line 15</o>
</level4>
<level4>
    <m>line 13</m>
    <n>line 14</n>
    <o>line 15</o>
</level4>
<level21>
    <d>line 214</d>
    <e>line 215</e>
    <f>line 216</f>
    <g>line 217</g>
</level21>
<level2>
    <d>line 19</d>
    <e>line 20</e>
    <f>line 21</f>
    <g>line 22</g>
</level2>
<level3>
    <h>line 23</h>
    <i>line 24</i>
    <j>line 25</j>
    <k>line 26</k>
    <l>line 27</l>
</level3>
<level4>
    <m>line 28</m>
    <n>line 29</n>
    <o>line 30</o>
</level4>
<level4>
    <m>line 13</m>
    <n>line 14</n>
    <o>line 15</o>
</level4>
<level21>
    <d>line 224</d>
    <e>line 225</e>
    <f>line 226</f>
    <g>line 227</g>
</level21>
</root>

I get the result

<root>
   <root1>1234</root1>
   <root2>5678</root2>
   <level1>
      <a>line 1</a>
      <b>line 2</b>
      <c>line 3</c>
      <level2>
         <d>line 4</d>
         <e>line 5</e>
         <f>line 6</f>
         <g>line 7</g>
         <level3>
            <SequenceCount>1</SequenceCount>
            <h>line 8</h>
            <i>line 9</i>
            <j>line 10</j>
            <k>line 11</k>
            <l>line 12</l>
            <level4>
               <SequenceCount>1</SequenceCount>
               <m>line 13</m>
               <n>line 14</n>
               <o>line 15</o>
            </level4>
            <level4>
               <SequenceCount>2</SequenceCount>
               <m>line 13</m>
               <n>line 14</n>
               <o>line 15</o>
            </level4>
         </level3>
      </level2>
      <level21>
         <SequenceCount>2</SequenceCount>
         <d>line 214</d>
         <e>line 215</e>
         <f>line 216</f>
         <g>line 217</g>
      </level21>
      <level2>
         <d>line 19</d>
         <e>line 20</e>
         <f>line 21</f>
         <g>line 22</g>
         <level3>
            <SequenceCount>3</SequenceCount>
            <h>line 23</h>
            <i>line 24</i>
            <j>line 25</j>
            <k>line 26</k>
            <l>line 27</l>
            <level4>
               <SequenceCount>1</SequenceCount>
               <m>line 28</m>
               <n>line 29</n>
               <o>line 30</o>
            </level4>
            <level4>
               <SequenceCount>2</SequenceCount>
               <m>line 13</m>
               <n>line 14</n>
               <o>line 15</o>
            </level4>
         </level3>
      </level2>
      <level21>
         <SequenceCount>4</SequenceCount>
         <d>line 224</d>
         <e>line 225</e>
         <f>line 226</f>
         <g>line 227</g>
      </level21>
   </level1>
</root>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Sorry but if you post a sample I can only write code against the sample, not against an imaginary one. I have added the code to compute the sequence number, based on the code in your stylesheet, I will edit to show that. If you have a completely different input sample then ask a new question. – Martin Honnen Apr 30 '13 at 16:57
  • Thanks for response, Martin. It works with the sample xml but I can't use concatenation to drill down the various element levels since those are not the real element names. What if the hierarchy is the same but without the incremental level numbers. e.g level1 -> A, level2-> B, level-> C, etc. – user2300314 Apr 30 '13 at 16:57
  • Will update the sample xml accordingly. – user2300314 Apr 30 '13 at 16:58