I am trying to write an XSLT template for the following XML structure:
<dealership>
<division>
<division_name>BMW</division_name>
<models>
<model_no>328i</model_no>
<model_no>M3</model_no>
<model_no>X5</model_no>
<model_no>528i</model_no>
</models>
<salesman>
<salesman_name>Bob</salesman_name>
<salesman_name>Jerry</salesman_name>
</salesman>
<mechanics>
<mechanic_name>Greg</mechanic_name>
<mechanic_name>Mike</mechanic_name>
<mechanic_name>Sean</mechanic_name>
</mechanics>
</division>
</dealership>
I need to output it to a HTML table in this format:
<table>
<tr>
<th>Division</th>
<th>Models</th>
<th>Salesman</th>
<th>Mechanics</th>
</tr>
<tr>
<td>BMW</td>
<td>328i</td>
<td>Bob</td>
<td>Greg</td>
</tr>
<tr>
<td></td>
<td>M3</td>
<td>Jerry</td>
<td>Mike</td>
</tr>
<tr>
<td></td>
<td>X5</td>
<td></td>
<td>Sean</td>
</tr>
<tr>
<td></td>
<td>528i</td>
<td></td>
<td></td>
</tr>
</table>
The problem is there can be any number of models, salesman, and mechanics. So somehow I need to get the node with the most children to know how many rows to create in the table, then I need a way to track which cells in the rows are empty. Any help would be greatly appreciated.
The best way I can think of to do it is to call a named template with the row number as a parameter. The template will then check to see if there is any data to output, and if so then build that row of the table and call itself with the next row number as a new parameter value.
This code demonstrates. The output is different from the HTML you show because yours doesn't correspond to the XML data you have given. As far as I can tell this is correct.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="html" />
<xsl:template match="/dealership/division">
<table>
<tr>
<th>Division</th>
<th>Models</th>
<th>Salesman</th>
<th>Mechanics</th>
</tr>
<xsl:call-template name="row" >
<xsl:with-param name="i" select="1" />
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="row">
<xsl:param name="i"/>
<xsl:if test="models/model_no[$i] |
salesman/salesman_name[$i] |
mechanics/mechanic_name[$i]">
<tr>
<td>
<xsl:value-of select="division_name[$i]"/>
</td>
<td>
<xsl:value-of select="models/model_no[$i]"/>
</td>
<td>
<xsl:value-of select="salesman/salesman_name[$i]"/>
</td>
<td>
<xsl:value-of select="mechanics/mechanic_name[$i]"/>
</td>
</tr>
<xsl:call-template name="row" >
<xsl:with-param name="i" select="$i + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
output
<table>
<tr>
<th>Division</th>
<th>Models</th>
<th>Salesman</th>
<th>Mechanics</th>
</tr>
<tr>
<td>BMW</td>
<td>328i</td>
<td>Bob</td>
<td>Greg</td>
</tr>
<tr>
<td></td>
<td>M3</td>
<td>Jerry</td>
<td>Mike</td>
</tr>
<tr>
<td></td>
<td>X5</td>
<td></td>
<td>Sean</td>
</tr>
</table>
Hear another possible solution.
<xsl:template match="/dealership/division">
<xsl:variable name="maxcnt">
<xsl:for-each select="*" >
<xsl:sort select="count(*)" order="descending"/>
<xsl:if test ="position()=1">
<xsl:value-of select="name(.)"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<table>
<tr>
<th>Division</th>
<th>Models</th>
<th>Salesman</th>
<th>Mechanics</th>
</tr>
<xsl:apply-templates select="*[name()=$maxcnt]/*" mode="row"/>
</table>
</xsl:template>
<xsl:template match="*" mode="row">
<xsl:variable name="pos" select="count(preceding-sibling::*)+1"/>
<tr>
<td>
<xsl:value-of select="../../division_name[$pos]"/>
</td>
<td>
<xsl:value-of select="../../models/model_no[$pos]"/>
</td>
<td>
<xsl:value-of select="../../salesman/salesman_name[$pos]"/>
</td>
<td>
<xsl:value-of select="../../mechanics/mechanic_name[$pos]"/>
</td>
</tr>
</xsl:template>
The first step is to figure out which child of division has the maximum count of children by it self. This is done by an for-each over all children sorted by count of children. Therefore the first on is the one with most children.
<xsl:for-each select="*" >
<xsl:sort select="count(*)" order="descending"/>
<xsl:if test ="position()=1">
<xsl:value-of select="name(.)"/>
</xsl:if>
</xsl:for-each>
This is based on a very good explanation from Dimitre Novatchev(*)
Related
I am trying to build an html table from an xml document using xslt. The xml-code looks like this:
<root>
<group>
<name>A</name>
<item>1</item>
<item>2</item>
<item>3</item>
</group>
<group>
<name>B</name>
<item>4</item>
<item>5</item>
</group>
</root>
The table should look like this:
<table>
<tr>
<th>Group</th>
<th>Item</th>
</tr>
<tr class="row_0">
<td>A</td>
<td>1</td>
</tr>
<tr class="row_1">
<td>.</td>
<td>2</td>
</tr>
<tr class="row_0">
<td>.</td>
<td>3</td>
</tr>
<tr class="row_1">
<td>B</td>
<td>4</td>
</tr>
<tr class="row_0">
<td>.</td>
<td>5</td>
</tr>
</table>
The problem is the alternating of the class attribute (This is needed for styling, because I don't have access to modern CSS pseudoclasses) The class should always alternate between row_0 and row_1. Because of the layout of the table, it could be expressed as something like this:
<xsl:attribute name="class">row_<xsl:value-of select="count(all previous item elements) mod 2" /></xsl:attribute>
How can I express all previous item elements as a real selector? It also has to count all item elements in previous groups. However, it should only count up to root (root in this example is not the root of my actual document)
EDIT:
My current xslt looks like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="root">
<table>
<tr>
<th>Group</th>
<th>Item</th>
</tr>
<xsl:for-each select="group">
<tr>
<xsl:attribute name="class">row_<xsl:value-of select="position() mod 2" /></xsl:attribute>
<td><xsl:value-of select="name" /></td>
<xsl:for-each select="item">
<xsl:if test="position() = 1">
<td><xsl:value-of select="text()" /></td>
</xsl:if>
</xsl:for-each>
</tr>
<xsl:for-each select="item">
<xsl:if test="position() > 1">
<tr>
<xsl:attribute name="class">row_<xsl:value-of select="position() mod 2" /></xsl:attribute>
<td>.</td>
<td><xsl:value-of select="text()" /></td>
</tr>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
The two attributes where it says position() mod 2 would need to be replaced.
Let me suggest a simpler approach:
<xsl:template match="/root">
<table>
<tr>
<th>Group</th>
<th>Item</th>
</tr>
<xsl:for-each select="group/item">
<tr class="row_{(position() - 1) mod 2}">
<td>
<xsl:choose>
<xsl:when test="not(preceding-sibling::item)">
<xsl:value-of select="../name" />
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</td>
<td><xsl:value-of select="."/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
I have the following xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Loci>
<Locus>
<Id>MTBC1726</Id>
<Alleles>
<Allele>
<Name>1.0</Name>
<Description>No annotation provided</Description>
</Allele>
<Allele>
<Name>2.0</Name>
<Description>No annotation provided</Description>
</Allele>
</Alleles>
</Locus>
<Locus>
<Id>MTBC3142</Id>
<Alleles>
<Allele>
<Name>1.0</Name>
<Description>No annotation provided</Description>
</Allele>
</Alleles>
</Locus>
</Loci>
And I want to create the following result:
which, in HTML, would look like this:
<html>
<body>
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<tr>
<td rowspan="2">MTBC1726</td>
<td>1.0</td>
<td >No annotation provided</td>
</tr>
<tr>
<td>2.0</td>
<td >No annotation provided</td>
</tr>
<tr>
<td >MTBC3142</td>
<td>1.0</td>
<td >No annotation provided</td>
</tr>
</table>
</body>
</html>
I have created the following XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<xsl:for-each select="Loci/Locus">
<tr>
<td><xsl:value-of select="Id"/></td>
<xsl:for-each select="Alleles/Allele">
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="Description"/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
but that produces a table like this:
So, my question is: how do I add the rowspan attribute with a count based on the number of <Allele> nodes?
How about this:
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<xsl:for-each select="Loci/Locus">
<xsl:for-each select="Alleles/Allele">
<tr>
<xsl:if test="position() = 1">
<td rowspan="{last()}">
<xsl:value-of select="ancestor::Locus[1]/Id"/>
</td>
</xsl:if>
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="Description"/></td>
</tr>
</xsl:for-each>
</xsl:for-each>
</table>
The idea here is that within the inner for-each you can use position() to see whether you're on the first Allele for this Locus, and last() to give the number of Allele elements that the current Locus contains. The ancestor::Locus[1] is because we need to extract the Locus Id at a point where the current context is its first Allele.
If you want to avoid adding rowspan="1" for single-allele loci, you'll have to use a conditional xsl:attribute instead of an attribute value template:
<xsl:if test="position() = 1">
<td>
<xsl:if test="last() > 1">
<xsl:attribute name="rowspan">
<xsl:value-of select="last()" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="ancestor::Locus[1]/Id"/>
</td>
</xsl:if>
You could use the XSL count() function to define a variable, and then output that variable as your attribute value:
<xsl:variable name="recordCount" select="count(Alleles/Allele)"/>
<td rowspan="{$recordCount}"><xsl:value-of select="Id"/></td>
You can count the number of Allele and use that value to create your rowspan. I also put in a check so that it didn't add a rowspan=1 for cases when there was only the one Allele
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<xsl:for-each select="Loci/Locus">
<tr>
<td>
<xsl:if test="count(Alleles/Allele) > 1">
<xsl:attribute name="rowspan">
<xsl:value-of select="count(Alleles/Allele)"/>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="Id"/>
</td>
<xsl:for-each select="Alleles/Allele">
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="Description"/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
I'm new to xslt. I tried using urn:helper in the stylesheet tag. But it throws the following error.
"Cannot find the script or external object that implements prefix 'urn:Helper'".
Below is the snippet used in my code.
xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:myObj="urn:Helper"
xmlns:t="http://microsoft.com/schemas/VisualStudio/TeamTest/2006"
Am I missing something?
Thanks..
edit: complete stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:myObj="urn:Helper"
xmlns:t="http://microsoft.com/schemas/VisualStudio/TeamTest/2006">
<xsl:param name="today"></xsl:param>
<xsl:param name="results"></xsl:param>
<xsl:param name="pass" select="'Passed'"/>
<xsl:param name="fail" select="'Failed'"/>
<xsl:key name="class-key" match="#className" use="."/>
<xsl:variable name="unique-classes" select="//t:TestMethod/#className[generate-id(.) =generate-id(key('class-key',.))]" />
<xsl:template match="/">
<html>
<body style="font-family:Verdana; font-size:10pt">
<h1>Test Results Summary</h1>
<table style="font-family:Verdana; font-size:10pt">
<tr>
<td>
<b>Run Date/Time</b>
</td>
<td>
</td>
</tr>
<tr>
<td>
Start Time:
</td>
<td>
<xsl:value-of select="myObj:DateTimeToString(//t:TestRun/t:Times/#start)"/>
</td>
</tr>
<tr>
<td>
End Time:
</td>
<td>
<xsl:value-of select="myObj:DateTimeToString(//t:TestRun/t:Times/#finish)"/>
</td>
</tr>
<tr>
<td>
Duration:
</td>
<td>
<xsl:value-of select="myObj:TimeSpan(//t:TestRun/t:Times/#start,//t:TestRun/t:Times/#finish)"/>
</td>
</tr>
<tr>
<td>
<b>Results File</b>
</td>
<td>
<xsl:value-of select="$results"/>
</td>
</tr>
</table>
Coverage Summary
<xsl:call-template name="summary" />
<!--<xsl:call-template name="details" />-->
<xsl:call-template name="details2" />
</body>
</html>
</xsl:template>
<xsl:template name="summary">
<h3>Test Summary</h3>
<table style="width:640;border:1px solid black;font-family:Verdana; font-size:10pt">
<tr>
<td style="font-weight:bold">Total</td>
<td style="font-weight:bold">Failed</td>
<td style="font-weight:bold">Passed</td>
<td style="font-weight:bold">Inconclusive</td>
</tr>
<tr>
<td >
<xsl:value-of select="/t:TestRun/t:ResultSummary/t:Counters/#total"/>
</td>
<td style="background-color:pink;">
<xsl:value-of select="/t:TestRun/t:ResultSummary/t:Counters/#failed"/>
</td>
<td style="background-color:lightgreen;">
<xsl:value-of select="/t:TestRun/t:ResultSummary/t:Counters/#passed"/>
</td>
<td style="background-color:yellow;">
<xsl:value-of select="/t:TestRun/t:ResultSummary/t:Counters/#inconclusive"/>
</td>
</tr>
</table>
</xsl:template>
<xsl:template name="details">
<h3>Unit Test Results</h3>
<table style="width:640;border:1px solid black;font-family:Verdana; font-size:10pt;">
<tr>
<td style="font-weight:bold">Test Name</td>
<td style="font-weight:bold">Result</td>
<td style="font-weight:bold">Duration</td>
</tr>
<xsl:for-each select="/t:TestRun/t:Results/t:UnitTestResult">
<xsl:sort select="#testName"/>
<tr>
<xsl:attribute name="style">
<xsl:choose>
<xsl:when test="#outcome='Failed'">background-color:pink;</xsl:when>
<xsl:when test="#outcome='Passed'">background-color:lightgreen;</xsl:when>
<xsl:otherwise>background-color:yellow;</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<td>
<xsl:value-of select="#testName"/>
</td>
<td>
<xsl:choose>
<xsl:when test="#outcome='Failed'">FAILED</xsl:when>
<xsl:when test="#outcome='Passed'">Passed</xsl:when>
<xsl:otherwise>Inconclusive</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:value-of select="#duration"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="details2">
<h3>Unit Test Results</h3>
<table border="0" style="width:640;border:1px solid black;font-family:Verdana; font-size:10pt;">
<xsl:for-each select="$unique-classes">
<xsl:sort />
<xsl:variable name="curClass" select="."/>
<xsl:variable name="return" select="myObj:GetClassInformation($curClass)"/>
<!--<xsl:for-each select="//TestRun/tests/value/testMethod[className=$curClass]">-->
<tr>
<td valign="bottom" style="background-color:beige;font-weight:bold;" colspan="3">
<font>
<xsl:value-of select="concat('',$return/className)"/>
</font>
</td>
</tr>
<tr>
<td style="font-weight:bold">Test Name</td>
<td style="font-weight:bold">Result</td>
<td style="font-weight:bold">Duration</td>
</tr>
<xsl:for-each select="//t:UnitTest/t:TestMethod[#className=$curClass]">
<xsl:sort select="#name"/>
<xsl:variable name="testid" select="../#id"/>
<xsl:for-each select="//t:UnitTestResult[#testId=$testid]">
<xsl:call-template name="classRunsDetail">
<xsl:with-param name="testid" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
<tr>
<td style="border-bottom:0px solid black;height:1px;background-color:black" colspan="3"></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="classRunsDetail">
<xsl:param name="testid"/>
<tr>
<xsl:attribute name="style">
<xsl:choose>
<xsl:when test="#outcome = $fail">background-color:pink;</xsl:when>
<xsl:when test="#outcome = $pass">background-color:lightgreen;</xsl:when>
<xsl:otherwise>background-color:yellow;</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<td>
<xsl:value-of select="#testName"/>
</td>
<td>
<xsl:choose>
<xsl:when test="#outcome = $fail">FAILED</xsl:when>
<xsl:when test="#outcome = $pass">Passed</xsl:when>
<xsl:otherwise>Inconclusive</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:value-of select="#duration"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
There is no problem with your xsl:stylesheet element.
The problem is here:
<xsl:value-of select="myObj:DateTimeToString(//t:TestRun/t:Times/#start)"/>
From http://www.w3.org/TR/xslt#section-Extension-Functions
If a FunctionName in a FunctionCall
expression is not an NCName (i.e. if
it contains a colon), then it is
treated as a call to an extension
function. The FunctionName is expanded
to a name using the namespace
declarations from the evaluation
context.
If the XSLT processor does not have an
implementation of an extension
function of a particular name
available, then the function-available
function must return false for that
name. If such an extension function
occurs in an expression and the
extension function is actually called,
the XSLT processor must signal an
error.
The answer: You are missing the extension function implementation.
So, you must provide details of your processor and re-ask what is the specific way that your processor is linked to the implementation of extended functions.
I see you grabbed that little helper object from this post. It seems right, but make sure you are using the C# code to actually do the transform.
Could anyone help me with the following transformation?
Here is the XML
<Chart>
<Chart.Series>
<DataSeries LegendText="Complete On Time" >
<DataSeries.DataPoints>
<DataPoint AxisXLabel="Sep 09" YValue="10" />
<DataPoint AxisXLabel="Oct 09" YValue="11" />
<DataPoint AxisXLabel="Nov 09" YValue="12" />
</DataSeries.DataPoints>
</DataSeries>
<DataSeries LegendText="Complete Overdue" >
<DataSeries.DataPoints>
<DataPoint YValue="1" />
<DataPoint YValue="2" />
<DataPoint YValue="3" />
</DataSeries.DataPoints>
</DataSeries>
</Chart.Series>
</Chart>
and here is the output id like
<table>
<thead>
<tr>
<th></th>
<th>Complete On Time</th>
<th>Complete Overdue</th>
</tr>
</thead>
<tbody>
<tr>
<th>Sep 09</th>
<th>10</th>
<th>1</th>
</tr>
<tr>
<th>Oct 09</th>
<th>11</th>
<th>2</th>
</tr>
<tr>
<th>Nov 09</th>
<th>12</th>
<th>3</th>
</tr>
</tbody>
A more natural solution:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<!-- basic table structure -->
<xsl:template match="Chart">
<table>
<thead>
<xsl:apply-templates select="Chart.Series" mode="thead" />
</thead>
<tbody>
<xsl:apply-templates select="Chart.Series" mode="tbody" />
</tbody>
</table>
</xsl:template>
<!-- table head -->
<xsl:template match="Chart.Series" mode="thead">
<tr>
<th />
<xsl:for-each select="DataSeries">
<th>
<xsl:value-of select="#LegendText" />
</th>
</xsl:for-each>
</tr>
</xsl:template>
<!-- table body -->
<xsl:template match="Chart.Series" mode="tbody">
<xsl:variable name="ds" select="DataSeries" />
<!-- the first data series contains the labels -->
<xsl:for-each select="$ds[1]/*/DataPoint">
<xsl:variable name="pos" select="position()" />
<tr>
<td>
<xsl:value-of select="#AxisXLabel" />
</td>
<!-- process all data points at the current position -->
<xsl:apply-templates select="$ds/*/DataPoint[$pos]" />
</tr>
</xsl:for-each>
</xsl:template>
<!-- data points become a <td> -->
<xsl:template match="DataPoint">
<td>
<xsl:value-of select="#YValue" />
</td>
</xsl:template>
</xsl:stylesheet>
Note that I use template modes to do different things with the same input.
The result is:
<table>
<thead>
<tr>
<th />
<th>Complete On Time</th>
<th>Complete Overdue</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sep 09</td>
<td>10</td>
<td>1</td>
</tr>
<tr>
<td>Oct 09</td>
<td>11</td>
<td>2</td>
</tr>
<tr>
<td>Nov 09</td>
<td>12</td>
<td>3</td>
</tr>
</tbody>
</table>
This is something close. It's been a couple of year for me though that I've done XSLT - ignore bad style.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output indent="yes" omit-xml-declaration="yes" ></xsl:output>
<xsl:template match="/">
<table>
<thead>
<tr>
<th></th>
<xsl:apply-templates select=".//DataSeries" />
</tr>
</thead>
<tbody>
<xsl:apply-templates select="//DataSeries[1]//DataPoint">
<xsl:with-param name="datablock">1</xsl:with-param>
</xsl:apply-templates>
</tbody>
</table>
</xsl:template>
<xsl:template match="DataSeries">
<th>
<xsl:value-of select="#LegendText"></xsl:value-of>
</th>
</xsl:template>
<xsl:template match="DataPoint">
<xsl:param name="datablock" />
<xsl:variable name="posi" select="position()"></xsl:variable>
<xsl:if test="$datablock = 1">
<tr>
<xsl:for-each select="#*">
<th>
<xsl:value-of select="."></xsl:value-of>
</th>
</xsl:for-each>
<xsl:apply-templates select="//DataSeries[$datablock+1]//DataPoint[$posi]">
<xsl:with-param name="datablock" select="$datablock + 1">
</xsl:with-param>
</xsl:apply-templates>
</tr>
</xsl:if>
<xsl:if test="$datablock != 1">
<xsl:for-each select="#*">
<th>
<xsl:value-of select="."></xsl:value-of>
</th>
</xsl:for-each>
<xsl:apply-templates select="//DataSeries[$datablock+1]//DataPoint[$posi]">
<xsl:with-param name="datablock" select="$datablock + 1">
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
I'm trying to go from this kind of input:
<col title="one">
<cell>a</cell> <cell>b</cell> <cell>c</cell> <cell>d</cell>
</col>
<col title="two">
<cell>e</cell> <cell>f</cell> <cell>g</cell>
</col>
... to this HTML output with XSLT:
<table>
<tr> <th>one</th> <th>two</th> </tr>
<tr> <td>a</td> <td>e</td> </tr>
<tr> <td>b</td> <td>f</td> </tr>
<tr> <td>c</td> <td>g</td> </tr>
<tr> <td>d</td> </tr>
</table>
In other words I want to perform a matrix transposition. I couldn't find a simple way to do that, there probably isn't, I guess; how about a complicated one? While searching on Google I found hints that a way to solve this was through recursion. Any idea appreciated.
One possibility is to find the <col> with the most cells and then iterate over them in a nested loop. This guarantees the generation of a structurally valid HTML table.
<!-- this variable stores the unique ID of the longest <col> -->
<xsl:variable name="vMaxColId">
<xsl:for-each select="/root/col">
<xsl:sort select="count(cell)" data-type="number" order="descending" />
<xsl:if test="position() = 1">
<xsl:value-of select="generate-id()" />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- and this selects the children of that <col> for later iteration -->
<xsl:variable name="vIter" select="
/root/col[generate-id() = $vMaxColId]/cell
" />
<xsl:template match="root">
<xsl:variable name="columns" select="col" />
<table>
<!-- output the <th>s -->
<tr>
<xsl:apply-templates select="$columns/#title" />
</tr>
<!-- make as many <tr>s as there are <cell>s in the longest <col> -->
<xsl:for-each select="$vIter">
<xsl:variable name="pos" select="position()" />
<tr>
<!-- make as many <td>s as there are <col>s -->
<xsl:for-each select="$columns">
<td>
<xsl:value-of select="cell[position() = $pos]" />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="col/#title">
<th>
<xsl:value-of select="." />
</th>
</xsl:template>
Applied to
<root>
<col title="one">
<cell>a</cell> <cell>b</cell> <cell>c</cell> <cell>d</cell>
</col>
<col title="two">
<cell>e</cell> <cell>f</cell> <cell>g</cell>
</col>
</root>
this produces:
<table>
<tr>
<th>one</th> <th>two</th>
</tr>
<tr>
<td>a</td> <td>e</td>
</tr>
<tr>
<td>b</td> <td>f</td>
</tr>
<tr>
<td>c</td> <td>g</td>
</tr>
<tr>
<td>d</td> <td></td>
</tr>
</table>
From Marrow:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="input">
<table border="1">
<xsl:apply-templates select="col[1]/cell"/>
</table>
</xsl:template>
<xsl:template match="cell">
<xsl:variable name="curr-pos" select="position()"/>
<tr>
<td>
<xsl:copy-of select="node()|../following-sibling::col/cell[$curr-pos]/node()"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
I put input tags around your xml to make it closer match an example I found.
(getting closer).
BTW: you can test by adding this as your 2nd line to your xml:
<?xml-stylesheet type="text/xsl" href="NonLinear.xslt"?>