[xsl] Flattening, grouping & ordering elements

Subject: [xsl] Flattening, grouping & ordering elements
From: "Fjoerie" <Fjoerie@xxxxxxxxxx>
Date: Mon, 26 Apr 2010 12:14:13 +0000
Hi,

Given the following (simplified) input:
<a>
  <b id="b1">
    <c>c1</c>
  </b>
  <b id="b2">
    <c>c2</c>
  </b>
  <b id="b3" special="true">
    <c>c3</c>
  </b>
  <d>d1</d>
  <d>d2</d>
  <e>e1</e>
  <e>e2</e>
  <f id="f1">
    <c>c4</c>
  </f>
  <f id="f2">
    <c>c5</c>
  </f>
  <f id="f3">
    <c>c6</c>
  </f>
  <c>c7</c>
</a>

I need to produce the following output:

x: f1
-----
   c4
-----
   f2
-----
   c5
-----
   f3
-----
   c6
-----
   b1
-----
   c1
-----
   b2
-----
   c2
-----
   b3
-----
   special
-----
   c3
-----
   c7
-----
   e1
-----
   e2
-----
   d1
-----
   d2

Where x is a fixed label that needs to be output on the first line.
Any of the elements could be missing, so x could just as well have to be
output in front of "d1". Outputting the data could be as easy as nesting the
necessary for loops, but the problems I'm facing is how to output the x
without having countless checks to see if elements are missing and how to find
the last line that can't have a border (in reality all of the above is
outputted in a table, where the style of the last line needs to be different,
the label is in a first column & the data in a second column). For example:
the following outputs the elements in the correct order contains no lines. It
will also be hard to maintain if at one point the order of the data should be
output differently due to the if tests to output the label.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="1.0">
  <xsl:output method="text" encoding="ASCII"/>
  <xsl:template match="/">
    <xsl:apply-templates select="a"/>
  </xsl:template>
  <xsl:template match="a">
    <xsl:if test="count(f) > 0">
      <xsl:text>x: </xsl:text>
    </xsl:if>
    <xsl:for-each select="f">
      <xsl:apply-templates select="."/>
      <xsl:for-each select="c">
        <xsl:apply-templates select="."/>
      </xsl:for-each>
    </xsl:for-each>
    <xsl:if test="(count(f) = 0) and (count(b) > 0)">
      <xsl:text>x: </xsl:text>
    </xsl:if>
    <xsl:for-each select="b">
      <xsl:apply-templates select="."/>
      <xsl:for-each select="c">
        <xsl:apply-templates select="."/>
      </xsl:for-each>
    </xsl:for-each>
    <xsl:if test="(count(f) = 0) and (count(b) = 0) and (count(c) > 0)">
      <xsl:text>x: </xsl:text>
    </xsl:if>
    <xsl:apply-templates select="c"/>
    <xsl:if test="(count(f) = 0) and (count(b) = 0) and (count(c) = 0) and
(count(e) > 0)">
      <xsl:text>x: </xsl:text>
    </xsl:if>
    <xsl:apply-templates select="e"/>
    <xsl:if test="(count(f) = 0) and (count(b) = 0) and (count(c) = 0) and
(count(e) = 0) and (count(d) > 0)">
      <xsl:text>x: </xsl:text>
    </xsl:if>
    <xsl:apply-templates select="d"/>
  </xsl:template>
  <xsl:template match="b|f">
    <xsl:value-of select="@id"/>
    <xsl:text>&#10;</xsl:text>
    <xsl:if test="@special = 'true'">
      <xsl:text>special&#10;</xsl:text>
    </xsl:if>
  </xsl:template>
  <xsl:template match="c|d|e">
    <xsl:value-of select="."/>
    <xsl:text>&#10;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

My second thought was to group everything in one large for-each, in which case
it would be easy to find the first & last line. But then I have no clue on how
to get the order of the elements in the output right:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="1.0">
  <xsl:output method="text" encoding="ASCII"/>
  <xsl:template match="/">
    <xsl:apply-templates select="a"/>
  </xsl:template>
  <xsl:template match="a">
    <xsl:for-each select="f|f/c|b|b/c|c|e|d">
      <xsl:if test="position() = 1">
        <xsl:text>x: </xsl:text>
      </xsl:if>
      <xsl:apply-templates select="."/>
      <xsl:if test="position() != last()">
        <xsl:text>-----&#10;</xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
  <xsl:template match="b|f">
    <xsl:value-of select="@id"/>
    <xsl:text>&#10;</xsl:text>
    <xsl:if test="@special = 'true'">
      <xsl:text>-----&#10;</xsl:text>
      <xsl:text>special&#10;</xsl:text>
    </xsl:if>
  </xsl:template>
  <xsl:template match="c|d|e">
    <xsl:value-of select="."/>
    <xsl:text>&#10;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Any suggestions on how to order the set in the second one?

Thanks in advances,

Fjoerie

Current Thread