xslt to create dynamic table with customizable headers - xslt

I would like to transform an XML document using xslt with a set of row nodes and column nodes into an xhtml table.
The column nodes define data about the associated row attribute. For example, the first column node specifies that the ID attribute of the Row node should be hidden (i.e. not show up in the table). The Caption element of the Column node defines what the column header text should be.
I've seen solutions where you know the attributes that you want to turn into columns ahead of time, but I'm not sure how to use the relevant column data to format the headers
Input:
<TableData>
<Columns>
<Column Name="ID" Hidden="true" />
<Column Name="Name" Caption="Item Name" />
<Column Name="Desc" Caption="Item Description" />
</Columns>
<Rows>
<Row ID="0" Name="A" />
<Row ID="1" Name="B" Desc="Some description"/>
<Row ID="3" Name="C" />
</Rows>
</TableData>
Desired output would be a table in (x)html something like this:
Item Name | Item Description
--------------------------------------
A |
B | Some Description
C |

This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="RowAttribsByName" match="Row/#*"
use="concat(generate-id(..), '|', name())"/>
<xsl:variable name="vColNames" select=
"/*/Columns/*[not(#Hidden = 'true')]/#Name"/>
<xsl:template match="/*">
<table border="1">
<tr>
<xsl:apply-templates select="Columns/*"/>
</tr>
<xsl:apply-templates select="Rows/Row"/>
</table>
</xsl:template>
<xsl:template match="Column[not(#Hidden = 'true')]">
<td><xsl:value-of select="#Caption"/></td>
</xsl:template>
<xsl:template match="Row">
<tr>
<xsl:apply-templates select="$vColNames">
<xsl:with-param name="pRowId"
select="generate-id()"/>
</xsl:apply-templates>
</tr>
</xsl:template>
<xsl:template match="Column/#*">
<xsl:param name="pRowId"/>
<td width="50%">
<xsl:value-of select=
"key('RowAttribsByName',
concat($pRowId, '|', .)
)
"/>
</td>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<TableData>
<Columns>
<Column Name="ID" Hidden="true" />
<Column Name="Name" Caption="Item Name" />
<Column Name="Desc" Caption="Item Description" />
</Columns>
<Rows>
<Row ID="0" Name="A" />
<Row ID="1" Name="B" Desc="Some description"/>
<Row ID="3" Name="C" />
</Rows>
</TableData>
produces the wanted, correct result:
<table border="1">
<tr>
<td>Item Name</td>
<td>Item Description</td>
</tr>
<tr>
<td width="50%">A</td>
<td width="50%"/>
</tr>
<tr>
<td width="50%">B</td>
<td width="50%">Some description</td>
</tr>
<tr>
<td width="50%">C</td>
<td width="50%"/>
</tr>
</table>

Related

Advanced xsl loop

I would like to build a table for each JOB of type T. This table must have 2 columns (LPATH and RPATH). LPATH1 must be on the same row as RPATH1, etc...
<ROOT>
<FOLDER>
<JOB type="T">
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/aL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/aR" />
<VARIABLE NAME="%%FTP-LPATH2" VALUE="/path/to/bL" />
<VARIABLE NAME="%%FTP-RPATH2" VALUE="/path/to/bR" />
</JOB>
<JOB type="O" />
<JOB type="T">
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/eL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/eR" />
</JOB>
</FOLDER>
<FOLDER>
<JOB type="O" />
<JOB type="T">
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/cL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/cR" />
<VARIABLE NAME="%%FTP-LPATH2" VALUE="/path/to/dL" />
<VARIABLE NAME="%%FTP-RPATH2" VALUE="/path/to/dR" />
<VARIABLE NAME="%%FTP-LPATH3" VALUE="/path/to/fL" />
<VARIABLE NAME="%%FTP-RPATH3" VALUE="/path/to/fR" />
</JOB>
<JOB type="T">
<VARIABLE NAME="%%FTP-RPATH5" VALUE="/path/to/kR" />
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/gL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/gR" />
<VARIABLE NAME="%%FTP-LPATH2" VALUE="/path/to/hL" />
<VARIABLE NAME="%%FTP-RPATH2" VALUE="/path/to/hR" />
<VARIABLE NAME="%%FTP-LPATH3" VALUE="/path/to/iL" />
<VARIABLE NAME="%%FTP-RPATH3" VALUE="/path/to/iR" />
<VARIABLE NAME="%%FTP-LPATH4" VALUE="/path/to/jL" />
<VARIABLE NAME="%%FTP-RPATH4" VALUE="/path/to/jR" />
<VARIABLE NAME="%%FTP-LPATH5" VALUE="/path/to/kL" />
</JOB>
<JOB type="O" />
</FOLDER>
</ROOT>
to obtain something like this:
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<tr>
<td>/path/to/aL</td>
<td>/path/to/aR</td>
</tr>
<tr>
<td>/path/to/bL</td>
<td>/path/to/bR</td>
</tr>
...
number of RPATHx/LPATHx couple is not determined
What could you suggest ?
Best regards
If they always come as left/right pairs, you could do simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/JOB">
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:for-each select="VARIABLE[contains(#NAME, 'LPATH')]">
<tr>
<td>
<xsl:value-of select="#VALUE"/>
</td>
<td>
<xsl:value-of select="following-sibling::VARIABLE[1]/#VALUE"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
or even simpler:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/JOB">
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:for-each select="VARIABLE[position() mod 2 = 1]">
<tr>
<td>
<xsl:value-of select="#VALUE"/>
</td>
<td>
<xsl:value-of select="following-sibling::VARIABLE[1]/#VALUE"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Added:
If they can come out of order, as shown in your updated example, then try:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="r" match="VARIABLE[starts-with(#NAME, '%%FTP-RPATH')]" use="substring-after(#NAME, '%%FTP-RPATH')" />
<xsl:template match="/ROOT">
<root>
<xsl:for-each select="FOLDER/JOB[#type='T']">
<xsl:variable name="job" select="." />
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:for-each select="VARIABLE[starts-with(#NAME, '%%FTP-LPATH')]">
<tr>
<td>
<xsl:value-of select="#VALUE"/>
</td>
<td>
<xsl:value-of select="key('r', substring-after(#NAME, '%%FTP-LPATH'), $job)/#VALUE"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/6pS26mt
Note that this still assumes that they come in pairs - or at least that the LPATH value of the pair will always be present.
Assuming VARIABLE/#NAME always contain LPATHnnn and RPATHnnn as their name :
<xsl:template match="/">
<html>
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:apply-templates select="JOB/VARIABLE[contains(#NAME,'LPATH')]"/>
</table>
</html>
</xsl:template>
<xsl:template match="VARIABLE">
<tr>
<th><xsl:value-of select="#VALUE"/></th>
<xsl:variable name="Rname" select="replace(#NAME,'LPATH','RPATH')"/>
<th><xsl:value-of select="//JOB/VARIABLE[#NAME=$Rname]/#VALUE"/></th>
</tr>
</xsl:template>
See it working here : https://xsltfiddle.liberty-development.net/pNmC4HG

xsl:key how to select unmatched key values

I am trying to compare two xml using xsl:key, but not sure how to print unmatched keys. In this scenario I am keying b.xml and comparing with a.xml, but it does not print unmatched keys from b.xml.
a.xml
<root>
<metas>
<meta>
<name>x</name>
<value>0</value>
</meta>
<meta>
<name>y</name>
<value>1</value>
</meta>
<meta>
<name>z</name>
<value>1</value>
</meta>
</metas>
b.xml
<root>
<metas>
<info>
<name>a</name>
<value>0</value>
</info>
<info>
<name>y</name>
<value>1</value>
</info>
<info>
<name>z</name>
<value>1</value>
</info>
</metas>
Desired output:
Table
a.xml b.xml
name missing in a.xml a
value missing in a.xml 0
name x missing in b.xml
value 0 missing in b.xml
name y y
value 1 1
name z z
value 1 1
My xsl;
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:param name="uri" as="xs:string" select="'b.xml'"/>
<xsl:param name="b" as="document-node()" select="doc($uri)"/>
<xsl:key name="bCompare" match="root/metas/info" use="name"/>
<xsl:template match="/">
<html>
<head>
<title>Test</title>
</head>
<body>
<table>
<tr>
<td></td>
<th>a.xml</th>
<th>b.xml</th>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="metas">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="meta">
<xsl:variable name="compare" select="key('bCompare', name, $b)"/>
<tr>
<th>Name</th>
<td>
<xsl:value-of select="name"/>
</td>
<td>
<xsl:value-of select="$compare/name"/>
<xsl:if test="empty($compare/name)">missing in b.xml</xsl:if>
</td>
</tr>
<tr>
<th>Value</th>
<td>
<xsl:value-of select="value"/>
</td>
<td>
<xsl:value-of select="$compare/value"/>
<xsl:if test="empty($compare/value)">missing in b.xml</xsl:if>
</td>
</tr>
</xsl:template>
You need to define a key for the other direction as well, i.e. <xsl:key name="aCompare" match="root/metas/meta" use="name"/>, then you need to make sure you process those elements of the second document with e.g. <xsl:apply-templates/><xsl:variable name="main-doc" select="/"/><xsl:apply-templates select="$b//info/meta[not(name = key('bCompare', $main-doc//meta/name, $main-doc))]"/> and then you need a template
<xsl:template match="info">
<xsl:variable name="compare" select="key('aCompare', name, $main-doc)"/>
<tr>
<th>Name</th>
<td>
<xsl:value-of select="$compare/name"/>
<xsl:if test="empty($compare/name)">missing in a.xml</xsl:if>
</td>
<td>
<xsl:value-of select="name"/>
</td>
</tr>
<tr>
<th>Value</th>
<td>
<xsl:value-of select="$compare/value"/>
<xsl:if test="empty($compare/value)">missing in a.xml</xsl:if>
</td>
<td>
<xsl:value-of select="value"/>
</td>
</tr>
</xsl:template>

xslt multiple grouping Format data in a table some columns are blank [duplicate]

This question already has an answer here:
xslt Format data in a table some columns are blank
(1 answer)
Closed 9 years ago.
With XSLT 1.0, how can I change the following:
<root>
<element id="1" Team="Rangers" Season="2011" Points="12" />
<element id="2" Team="Rangers" Season="2012" Points="5" />
<element id="3" Team="Rangers" Season="2012" Points="4" />
<element id="4" Team="Rangers" Season="2013" Points="3" />
<element id="5" Team="Astros" Season="2011" Points="12" />
<element id="6" Team="Astros" Season="2013" Points="2" />
<element id="7" Team="Astros" Season="2013" Points="1" />
<element id="8" Team="Astros" Season="2013" Points="2" />
</root>
Into:
<table>
<tr><td>Team</td>
<td>2011</td>
<td>2012</td>
<td>2013</td>
<td>Total</td></tr>
<tr><td>Rangers</td>
<td>12</td>
<td>9</td>
<td>3</td>
<td>20</td></tr>
<tr><td>Astros</td>
<td>12</td>
<td>0</td>
<td>5</td>
<td>14</td></tr>
</table>
I asked a similar question here but missed an important detail.
xslt Format data in a table some columns are blank
Notice that the sub groupings also has a summary.
Any help would be greatly appreciated!
I'm not entirely sure how this question is different from your last one, but this should do it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kTeam" match="element/#Team" use="."/>
<xsl:key name="kSeason" match="element/#Season" use="."/>
<xsl:variable name="distinctSeasons"
select="/root/element/#Season
[generate-id() = generate-id(key('kSeason', .)[1])]" />
<xsl:template match="root">
<table>
<tbody>
<tr>
<td>Team</td>
<xsl:apply-templates select="$distinctSeasons" mode="header">
<xsl:sort select="." data-type="number" />
</xsl:apply-templates>
<td>Total</td>
</tr>
<xsl:apply-templates
select="element/#Team[generate-id() =
generate-id(key('kTeam', .)[1])]" />
</tbody>
</table>
</xsl:template>
<xsl:template match="#Team">
<xsl:variable name="thisTeamRows" select="key('kTeam', .)/.." />
<tr>
<td>
<xsl:value-of select="." />
</td>
<xsl:apply-templates select="$distinctSeasons" mode="total">
<xsl:sort select="." data-type="number" />
<xsl:with-param name="teamRows" select="$thisTeamRows" />
</xsl:apply-templates>
<td>
<xsl:value-of select="sum($thisTeamRows/#Points)" />
</td>
</tr>
</xsl:template>
<xsl:template match="#Season" mode="header">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
<xsl:template match="#Season" mode="total">
<xsl:param name="teamRows" />
<td>
<xsl:value-of select="sum($teamRows[#Season = current()]/#Points)"/>
</td>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces:
<table>
<tbody>
<tr>
<td>Team</td>
<td>2011</td>
<td>2012</td>
<td>2013</td>
<td>Total</td>
</tr>
<tr>
<td>Rangers</td>
<td>12</td>
<td>9</td>
<td>3</td>
<td>24</td>
</tr>
<tr>
<td>Astros</td>
<td>12</td>
<td>0</td>
<td>5</td>
<td>17</td>
</tr>
</tbody>
</table>

XSLT dynamically creating rows and columns

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>
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:
n: number of current row, starting at 1
M: number of columns, calculated from the attribute #columns divided by two, because each <item /> results in two <td /> elements.
N: number of rows, calculated from the number of <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.
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>
Explanation
You can very simple use position() to structure the output into a matrix. This is far preferable to a convoluted call-template with many parameters.
Use the 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>).
Use the Piez Method to emit the odd empty table cells on the last row of the table.
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>

Convert column-oriented data to row-oriented HTML

My source XML provides data column-by-column; the cells have varying heights. Using XSLT 1.0, I need to pivot this around to match HTML's row-by-row table format.
Example: I need to convert input data like this
<source>
<column>
<cell height="1">col A row 1</cell>
<cell height="2">col A rows 2-3</cell>
</column>
<column>
<cell height="1">col B row 1</cell>
<cell height="1">col B row 2</cell>
<cell height="1">col B row 3</cell>
</column>
<column>
<cell height="3">col C rows 1-3</cell>
</column>
</source>
into an HTML table like this
<table>
<tr>
<td rowspan="1">col A row 1</td>
<td rowspan="1">col B row 1</td>
<td rowspan="3">col C rows 1-3</td>
</tr>
<tr>
<td rowspan="2">col A rows 2-3</td>
<td rowspan="1">col B row 2</td>
</tr>
<tr>
<td rowspan="1">col B row 3</td>
</tr>
</table>
How?
Edit: Here is another example, one where no single column has as many cells as there are table rows.
<source>
<column id="A">
<cell height="1">col A row 1</cell>
<cell height="2">col A rows 2-3</cell>
</column>
<column id="B">
<cell height="2">col B row 1-2</cell>
<cell height="1">col B row 3</cell>
</column>
<column id="C">
<cell height="3">col C rows 1-3</cell>
</column>
</source>
into an HTML table like this
<table>
<tr>
<td rowspan="1">col A row 1</td>
<td rowspan="2">col B row 1-2</td>
<td rowspan="3">col C rows 1-3</td>
</tr>
<tr>
<td rowspan="2">col A rows 2-3</td>
</tr>
<tr>
<td rowspan="1">col B row 3</td>
</tr>
</table>
I. XSLT 1.0 solution
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfPass1">
<xsl:apply-templates mode="pass1"/>
</xsl:variable>
<xsl:variable name="vPass1"
select="ext:node-set($vrtfPass1)"/>
<xsl:variable name="vMaxRow">
<xsl:for-each select=
"$vPass1/*/*/cell/#startRow">
<xsl:sort select="." data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="node()|#*" mode="pass1">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="pass1"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell" mode="pass1">
<cell height="{#height}"
startRow="{sum(preceding-sibling::*/#height) +1}">
<xsl:copy-of select="text()"/>
</cell>
</xsl:template>
<xsl:template match="/">
<table>
<xsl:call-template name="makeRows">
<xsl:with-param name="pmaxRow" select="$vMaxRow"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="makeRows">
<xsl:param name="prowNum" select="1"/>
<xsl:param name="pmaxRow" select="1"/>
<xsl:if test="not($prowNum > $pmaxRow)">
<tr>
<xsl:apply-templates select=
"$vPass1/*/*/cell[#startRow = $prowNum]"/>
</tr>
<xsl:call-template name="makeRows">
<xsl:with-param name="prowNum" select="$prowNum+1"/>
<xsl:with-param name="pmaxRow" select="$pmaxRow"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="cell">
<td rowspan="{#height}"><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<source>
<column>
<cell height="1">col A row 1</cell>
<cell height="2">col A rows 2-3</cell>
</column>
<column>
<cell height="2">col B row 1-2</cell>
<cell height="1">col B row 3</cell>
</column>
<column>
<cell height="3">col C rows 1-3</cell>
</column>
</source>
produces the wanted, correct result:
<table>
<tr>
<td rowspan="1">col A row 1</td>
<td rowspan="2">col B row 1-2</td>
<td rowspan="3">col C rows 1-3</td>
</tr>
<tr>
<td rowspan="2">col A rows 2-3</td>
</tr>
<tr>
<td rowspan="1">col B row 3</td>
</tr>
</table>
II. XSLT 2.0 solution:
This is similar to the XSLT 1.0 solution, but we use the max() function and also avoid the recursion by usig the to operator.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vPass1">
<xsl:apply-templates mode="pass1"/>
</xsl:variable>
<xsl:variable name="vMaxRow" as="xs:integer"
select="max($vPass1/*/*/cell/#startRow/xs:integer(.))"/>
<xsl:template match="node()|#*" mode="pass1">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="pass1"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell" mode="pass1">
<cell height="{#height}"
startRow="{sum(preceding-sibling::*/#height) +1}">
<xsl:copy-of select="text()"/>
</cell>
</xsl:template>
<xsl:template match="/">
<table>
<xsl:for-each select="1 to $vMaxRow">
<tr>
<xsl:apply-templates select=
"$vPass1/*/*/cell[#startRow = current()]"/>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="cell">
<td rowspan="{#height}"><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>