3

I’ve got some hierarchical XML like this:

<node text="a" value="1">
  <node text="gga" value="5">  
    <node text="dh" value="9">
      <node text="tyfg" value="4">  
      </node>  
    </node>  
  </node>  
  <node text="dfhgf" value="7">  
    <node text="fdsg" value="2">  
    </node>  
  </node>  
</node>

The names of the elements are the same all the way down (“node”), and the depth of the hierarchy isn’t known beforehand – in the above sample the deepest leaf is four down, but it can be of any depth.

What I need to do is take this XML and flatten it into a HTML table. The number of columns in the table should equal the depth of the deepest element, plus a column for the value attribute of each element. The "value" should appear in the rightmost column of the table, so the output rows cannot have ragged edges. There should be a row for each node regardless of what level it’s at. The above example should be transformed into:

<table>
  <tr>
    <td>a</td>
    <td></td>
    <td></td>
    <td></td>
    <td>1</td>
  </tr>
  <tr>
    <td>a</td>
    <td>gga</td>
    <td></td>
    <td></td>
    <td>5</td>
  </tr>
  <tr>
    <td>a</td>
    <td>gga</td>
    <td>dh</td>
    <td></td>
    <td>9</td>
  </tr>
  <tr>
    <td>a</td>
    <td>gga</td>
    <td>dh</td>
    <td>tyfg</td>
    <td>4</td>
  </tr>
  <tr>
    <td>a</td>
    <td>dfhgf</td>
    <td></td>
    <td></td>
    <td>7</td>
  </tr>
  <tr>
    <td>a</td>
    <td>dfhgf</td>
    <td>fdsg</td>
    <td></td>
    <td>2</td>
  </tr>
</table>

Anybody got some clever XSLT that can achieve this?

2
  • It looks like you're trying to visualize the structure, repeating the chain of parent elements on each row. Do you really need it to be a table? The result doesn't seem like actual tabular data to me. Commented Jun 4, 2009 at 11:07
  • Yes, the goal is to visualize the structure. A table seemed like the best choice to me, but my mind is open to alternative suggestions. As you say, what I really need is a chain of elements on each row. Commented Jun 4, 2009 at 11:27

2 Answers 2

3

It's not quite what you need (because it leaves a jagged table) but it'll still work in html

<xsl:template match="/">
    <html>
        <head>
        </head>
        <body>
            <table>
                <xsl:apply-templates select="//node" mode="row" />
            </table>
        </body>
    </html>
</xsl:template>

<xsl:template match="node" mode="row">
    <tr>
        <xsl:apply-templates select="ancestor-or-self::node" mode="popcell"/>   
        <xsl:apply-templates select="node[1]" mode="emptycell"/>
    </tr>
</xsl:template>

<xsl:template match="node" mode="popcell">
    <td><xsl:value-of select="@text"/></td>
</xsl:template>

<xsl:template match="node" mode="emptycell">
    <td></td>
    <xsl:apply-templates select="node[1]" mode="emptycell"/>
</xsl:template>

Version 2: Well I'm considerably less self-satisfied with it :P , but the following removes the jaggedness:

<xsl:variable name="depth">
    <xsl:for-each select="//node">
        <xsl:sort select="count(ancestor::node)" data-type="number" order="descending"/>
        <xsl:if test="position()=1">
            <xsl:value-of select="count(ancestor::node)+1"/>
        </xsl:if>
    </xsl:for-each>
</xsl:variable>

<xsl:template match="/">
    <html>
        <head>
        </head>
        <body>
            <table>
                <xsl:apply-templates select="//node" mode="row" />
            </table>
        </body>
    </html>
</xsl:template>

<xsl:template match="node" mode="row">
    <tr>
        <xsl:apply-templates select="ancestor-or-self::node" mode="popcell"/>
        <xsl:call-template name="emptycells">
            <xsl:with-param name="n" select="($depth)-count(ancestor-or-self::node)"/>
        </xsl:call-template>
        <td><xsl:value-of select="@value"/></td>
    </tr>
</xsl:template>

<xsl:template match="node" mode="popcell">
    <td><xsl:value-of select="@text"/></td>
</xsl:template>

<xsl:template name="emptycells">
    <xsl:param name="n" />
    <xsl:if test="$n &gt; 0">
        <td></td>   
        <xsl:call-template name="emptycells">
            <xsl:with-param name="n" select="($n)-1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks :) Sometimes the elegance of XSLT really is astonishing.
Hi annakata, thanks for this! It's close to what I need, unfortunately the jagged table is a problem. In my first draft of the question I simplified my problem. I've amended it now, so the reason I can't have jagged edges should be clear.
Bah, you've ruined my beautiful XSL.
Annakata, so sorry for ruining your beautiful XSL, but your 2nd version does exactly what I need - thank you so much!
0

This XSLT 1.0 solution would do it.

  • produces a well-formed HTML table
  • no recursion used

XSLT code:

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>

  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />

  <!-- some preparation -->
  <xsl:variable name="vAllNodes" select="//node" />

  <!-- find out the deepest nested node -->
  <xsl:variable name="vMaxDepth">
    <xsl:for-each select="$vAllNodes">
      <xsl:sort 
        select="count(ancestor::node)" 
        data-type="number" 
        order="descending" 
      />
      <xsl:if test="position() = 1">
        <xsl:value-of select="count(ancestor-or-self::node)" />
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <!-- select a list of nodes, merely to iterate over them -->
  <xsl:variable name="vIteratorList" select="
    $vAllNodes[position() &lt;= $vMaxDepth]
  " />

  <!-- build the table -->
  <xsl:template match="/">
    <table>
      <!-- the rows will be in document order -->
      <xsl:apply-templates select="$vAllNodes" />
    </table>
  </xsl:template>

  <!-- build the rows -->
  <xsl:template match="node">
    <xsl:variable name="self" select="." />
    <tr>
      <!-- iteration instead of recursion -->
      <xsl:for-each select="$vIteratorList">
        <xsl:variable name="vPos" select="position()" />
        <td>
          <!-- the ancestor axis is indexed the other way around -->
          <xsl:value-of select="
            $self/ancestor-or-self::node[last() - $vPos + 1]/@text
          " />
        </td>
      </xsl:for-each>
      <td>
        <xsl:value-of select="@value" />
      </td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

Output:

<table>
  <tr>
    <td>a</td>
    <td></td>
    <td></td>
    <td></td>
    <td>1</td>
  </tr>
  <tr>
    <td>a</td>
    <td>gga</td>
    <td></td>
    <td></td>
    <td>5</td>
  </tr>
  <tr>
    <td>a</td>
    <td>gga</td>
    <td>dh</td>
    <td></td>
    <td>9</td>
  </tr>
  <tr>
    <td>a</td>
    <td>gga</td>
    <td>dh</td>
    <td>tyfg</td>
    <td>4</td>
  </tr>
  <tr>
    <td>a</td>
    <td>dfhgf</td>
    <td></td>
    <td></td>
    <td>7</td>
  </tr>
  <tr>
    <td>a</td>
    <td>dfhgf</td>
    <td>fdsg</td>
    <td></td>
    <td>2</td>
  </tr>
</table>

5 Comments

Thanks for this Tomalak, it works very well. I made one tiny change to get the output I wanted - <xsl:value-of select="$vNodes[last() - $vPos + 1]/@text" /> changed to: <xsl:value-of select="$vNodes[$vPos]/@text" /> Otherwise the text cells in the row are in reverse order (i.e. leaf node text is in the leftmost column)
Actually, this ought not to be happening. $vNodes is made from the ancestor axis, meaning that $vNodes[1] should be the leaf. What XSLT processor are you using?
Hm. I've changed it so that it uses the "ancestor-or-self" axis directly instead of a variable. Can you try again?
I tried the change and now what I see is that every row in the output table contains the same text cells. The text in these cells corresponds to whatever the text of the longest path in the tree is. Hope I've explained that clearly!
Whoops, my bad. This has been fixed, try again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.