Reputation: 13
I have an XML file with following format:
<DataSet>
<Data id ="1" columns ="4">
<item name ="data1" value="value1"/>
<item name ="data2" value="value2"/>
<item name ="data3" value="value3"/>
<item name ="data4" value="value4"/>
<item name ="data5" value="value5"/>
</Data>
<Data id="2" columns ="2">
<item name ="data1" value="value1"/>
<item name ="data2" value="value2"/>
<item name ="data3" value="value3"/>
<item name ="data4" value="value4"/>
</Data>
</DataSet>
and I need an XSL transformation to get following table structure. Here the idea is to display the name and value attributes in two adjacent cells. So an 'item' will be associated with 2 columns and a row will be holding name/value pairs of two items. The number of columns will be specified in the Data element and will be always multiples of 2.
<report>
<table>
<tr>
<td>data1</td>
<td>value1</td>
<td>data2</td>
<td>value2</td>
</tr>
<tr>
<td>data3</td>
<td>value3</td>
<td>data4</td>
<td>value4</td>
</tr>
<tr>
<td>data5</td>
<td>value5</td>
<td></td>
<td></td>
</tr>
</table>
<table>
<tr>
<td>data1</td>
<td>value1</td>
</tr>
<tr>
<td>data2</td>
<td>value2</td>
</tr>
<tr>
<td>data3</td>
<td>value3</td>
</tr>
<tr>
<td>data4</td>
<td>value4</td>
</tr>
</table>
</report>
Upvotes: 1
Views: 9927
Reputation: 12729
Here is a much simpler solution.
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<report>
<xsl:apply-templates />
</report>
</xsl:template>
<xsl:template match="Data">
<xsl:variable name="cols" select="@columns" />
<table>
<xsl:for-each select="item[position()*2 mod $cols = (2 mod $cols)]">
<tr>
<xsl:for-each select="(.|following-sibling::item)
[ position()*2 <= $cols]">
<td><xsl:value-of select="@name" /></td>
<td><xsl:value-of select="@value" /></td>
</xsl:for-each>
<xsl:if test="position()=last()">
<xsl:for-each select="((/)//*)[position() <=
($cols - (count(.|following-sibling::item)*2))]">
<td />
</xsl:for-each>
</xsl:if>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
...when applied to this input...
<DataSet>
<Data id ="1" columns ="4">
<item name ="data1" value="value1"/>
<item name ="data2" value="value2"/>
<item name ="data3" value="value3"/>
<item name ="data4" value="value4"/>
<item name ="data5" value="value5"/>
</Data>
<Data id="2" columns ="2">
<item name ="data1" value="value1"/>
<item name ="data2" value="value2"/>
<item name ="data3" value="value3"/>
<item name ="data4" value="value4"/>
</Data>
</DataSet>
...yields...
<report>
<table>
<tr>
<td>data1</td>
<td>value1</td>
<td>data2</td>
<td>value2</td>
</tr>
<tr>
<td>data3</td>
<td>value3</td>
<td>data4</td>
<td>value4</td>
</tr>
<tr>
<td>data5</td>
<td>value5</td>
<td></td>
<td></td>
</tr>
</table>
<table>
<tr>
<td>data1</td>
<td>value1</td>
</tr>
<tr>
<td>data2</td>
<td>value2</td>
</tr>
<tr>
<td>data3</td>
<td>value3</td>
</tr>
<tr>
<td>data4</td>
<td>value4</td>
</tr>
</table>
</report>
position()
to structure the output into a matrix. This is far preferable to a convoluted call-template with many parameters.html
output method instead of the default xml
output method. This gives your html style of encoding for empty elements (like <td></td>
over xml style </td>
).Upvotes: 1
Reputation: 2869
The following XSL transformation applied to the provided input produces the desired output. Some explanation is provided below.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/DataSet"><report>
<xsl:apply-templates select="@*|node()" />
</report></xsl:template>
<xsl:template match="Data">
<xsl:variable name="count" select="count(item)" />
<xsl:variable name="M" select="@columns div 2" />
<xsl:variable name="N" select="($count + ($count mod $M)) div $M" />
<table>
<xsl:call-template name="nth-row">
<xsl:with-param name="n" select="1" />
<xsl:with-param name="M" select="$M" />
<xsl:with-param name="N" select="$N" />
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="nth-row">
<xsl:param name="n" />
<xsl:param name="N" />
<xsl:param name="M" />
<tr>
<xsl:call-template name="nmth-cell">
<xsl:with-param name="n" select="$n" />
<xsl:with-param name="m" select="1" />
<xsl:with-param name="N" select="$N" />
<xsl:with-param name="M" select="$M" />
</xsl:call-template>
</tr>
<xsl:if test="$N > $n">
<xsl:call-template name="nth-row">
<xsl:with-param name="n" select="$n + 1" />
<xsl:with-param name="N" select="$N" />
<xsl:with-param name="M" select="$M" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="nmth-cell">
<xsl:param name="n" />
<xsl:param name="m" />
<xsl:param name="N" />
<xsl:param name="M" />
<xsl:variable name="pos" select="($n - 1) * $M + $m" />
<xsl:choose>
<xsl:when test="item[position()=$pos]">
<td><xsl:value-of select="item[position()=$pos]/@name" /></td>
<td><xsl:value-of select="item[position()=$pos]/@value" /></td>
</xsl:when>
<xsl:otherwise>
<td></td>
<td></td>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$M > $m">
<xsl:call-template name="nmth-cell">
<xsl:with-param name="n" select="$n" />
<xsl:with-param name="m" select="$m + 1" />
<xsl:with-param name="N" select="$N" />
<xsl:with-param name="M" select="$M" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:transform>
Matching /DataSet
produces the root element <report />
and continues applying templates.
Matching Data
from inside /DataSet
produces a <table />
for each <Data />
element and then starts the interesting part by calling the template named nth-row
. The variables and parameters used are:
@columns
divided by two, because each <item />
results in two <td />
elements.<item />
elements present and divided by M. To account for div
truncating integer values the remainder $count mod $M
is added to $count
before.Now there come some recursive template calls. Each time nth-row
is called, it outputs a <tr />
and then calls nmth-cell
with appropriate parameters. As long as the current row is not the last one, nth-row
is recursively called with an incremented value of $n
.
Finally the template nmth-cell
each time it is called outputs two <td />
elements containing the values from the appropriate <item />
or nothing, if there is no corresponding <item />
. As long as the current column is not the last one, nmth-cell
is recursively called with an incremented value of $m
.
I hope this helps. Feel free to ask, if there is anything wrong with this or unclear to you.
Upvotes: 1
Reputation: 61
I don't know exactly what formatting you want but this should get you close i hope. It makes a table per data set with the data and value pairs next to each other. Just comment if you need formatting help too
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<report>
<xsl:for-each select="DataSet/Data">
<table>
<xsl:for-each select="item">
<tr>
<td><xsl:value-of select="@name"/></td>
<td><xsl:value-of select="@value"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</report>
</body>
</html>
</xsl:template>
Upvotes: 0