tags:

views:

250

answers:

2

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.

+1  A: 

From Marrow:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<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"?>
Kieveli
This gets you a good portion of the way. Can you get the rest of it?
Kieveli
That might get the ordering but doesn't generate the correct html grouped into rows.
AnthonyWJones
It generates them in rows, but not cells. I think the primary question was how to transpose, and not how to completely solve the problem.
Kieveli
Closer now, however an additional detail has appeared in answer a question I asked niXar. The first col may not have the most cells. This code assumes the first col can be used to enumerate the others. Whats needed is to discover the col with the maximum cells.
AnthonyWJones
+5  A: 

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>
Tomalak
this is exactly what I would have done. Nice work.
Jweede
+1. I was struggling with the col with the max cells bit. One outstanding issue is a HTML display issue, a TD with no content doesn't get rendered, swap the cols in the original XML around and you'll see what I mean.
AnthonyWJones
@AnthonyWJones: That's a merely presentational issue. *waves hand* :) A reasonable modern browser will understand the `empty-cells: show` CSS directive.
Tomalak