Reputation: 325
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?
Upvotes: 3
Views: 3970
Reputation: 338178
This XSLT 1.0 solution would do it.
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() <= $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>
Upvotes: 0
Reputation: 75794
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 > 0">
<td></td>
<xsl:call-template name="emptycells">
<xsl:with-param name="n" select="($n)-1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Upvotes: 3