xsl-list
[Top] [All Lists]

Flat to hierarchical with for-each-group (XSLT 2.0 solution)

2005-05-25 09:33:55
Nadia's question yesterday set me to thinking about how to turn flat XML 
into hierarchical XML by using XSLT 2.0's for-each-group instruction.

To simplify matters, I wrote a solution against a generic data structure 
rather than against Nadia's specific structure (from FrameMaker). Nadia: 
I'm sure you can translate this solution to your specific source files. If 
you do have trouble, though, I'll be happy to help.

I think this will become an increasingly common problem as more and more 
companies try to use FrameMaker and Word as front ends to XML-based 
single-sourcing document-production systems.

So, without further ado, here's the source file (flat as the proverbial 
pancake):

<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <heading1>H11</heading1>
  <para>P1</para>
  <para>P2</para>
  <para>P3</para>
  <heading2>H21</heading2>
  <para>P4</para>
  <para>P5</para>
  <heading3>H31</heading3>
  <para>P6</para>
  <heading2>H22</heading2>
  <para>P7</para>
  <para>P8</para>
  <heading1>H12</heading1>
  <para>P9</para>
</doc>

Here's the XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>

  <xsl:template match="doc">
    <doc>
      <xsl:for-each-group select="*" group-starting-with="heading1">
        <heading1 title="{current-group()[1]}">
          <xsl:for-each-group select="current-group()" 
group-starting-with="heading2">
            <xsl:choose>
              <xsl:when test="current-group()[1]/name() = 'heading2'">
                <heading2 title="{current-group()[1]}">
                  <xsl:for-each-group select="current-group()" 
group-starting-with="heading3">
                    <xsl:choose>
                      <xsl:when test="current-group()[1]/name() = 
'heading3'">
                        <heading3 title="{current-group()[1]}">
                          <xsl:apply-templates select="current-group()"/>
                        </heading3>
                      </xsl:when>
                      <xsl:otherwise>
                        <xsl:apply-templates select="current-group()"/>
                      </xsl:otherwise>
                    </xsl:choose>
                  </xsl:for-each-group>
                </heading2>
              </xsl:when>
              <xsl:otherwise>
                <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </heading1>
      </xsl:for-each-group>
    </doc>
  </xsl:template>

  <xsl:template match="heading1|heading2|heading3"/>

  <xsl:template match="para">
    <xsl:copy-of select="."/>
  </xsl:template>

</xsl:stylesheet>

And here's the result (now hierarchical):

<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <heading1 title="H11">
    <para>P1</para>
    <para>P2</para>
    <para>P3</para>
    <heading2 title="H21">
      <para>P4</para>
      <para>P5</para>
      <heading3 title="H31">
        <para>P6</para>
      </heading3>
    </heading2>
    <heading2 title="H22">
      <para>P7</para>
      <para>P8</para>
    </heading2>
  </heading1>
  <heading1 title="H12">
    <para>P9</para>
  </heading1>
</doc>

The only mystery to me is why I had to use test="current-group()[1]/name() 
= 'heading2'" rather than test="current-group()/heading2" or just 
test="heading2". I'd love to hear the explanation for that one.

Thanks.

Jay Bryant
Bryant Communication Services
(presently consulting at Synergistic Solution Technologies)

--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--



<Prev in Thread] Current Thread [Next in Thread>