xslt Format data in a table some columns are blank - xslt

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="2013" Points="3" />
<element id="4" Team="Astros" Season="2011" Points="12" />
<element id="5" 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>5</td>
<td>3</td>
<td>20</td></tr>
<tr><td>Astros</td>
<td>12</td>
<td>0</td>
<td>2</td>
<td>14</td></tr>
</table>
The heading can be generated manually as it is static, but I am unsure how to handle null data.
Any help would be greatly appreciated!

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="T" match="element" use="#Team"/>
<xsl:key name="S" match="element" use="#Season"/>
<xsl:template match="root">
<xsl:variable name="years" select="element[generate-id()=generate-id(key('S',#Season)[1])]"/>
<table>
<tbody>
<tr>
<td>Team</td>
<xsl:for-each select="$years">
<xsl:sort select="#Season"/>
<td><xsl:value-of select="#Season"/></td>
</xsl:for-each>
<td>Total</td>
</tr>
<xsl:for-each select="element[generate-id()=generate-id(key('T',#Team)[1])]">
<tr>
<xsl:variable name="thisteam" select="key('T',#Team)"/>
<xsl:for-each select="$years">
<xsl:sort select="#Season"/>
<td>
<xsl:choose>
<xsl:when test="$thisteam[#Season=current()/#Season]">
<xsl:value-of select="$thisteam[#Season=current()/#Season]/#Points"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</td>
</xsl:for-each>
<td><xsl:value-of select="sum($thisteam/#Points)"/></td>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
Produces
<?xml version="1.0" encoding="utf-8"?>
<table>
<tbody>
<tr>
<td>Team</td>
<td>2011</td>
<td>2012</td>
<td>2013</td>
<td>Total</td>
</tr>
<tr>
<td>12</td>
<td>5</td>
<td>3</td>
<td>20</td>
</tr>
<tr>
<td>12</td>
<td>0</td>
<td>2</td>
<td>14</td>
</tr>
</tbody>
</table>

Related

Split a tag inside foreach

I have a xml below
<Report>
<rl>
<id>12345;12346</id>
<activity>a2/a3</activity>
<result>r2/r3</result>
<operator>test</operator>
<timestamp>12/18/2014 3:51:19 PM</timestamp>
<quantity>2</quantity>
</rl>
<rl>
<id>22345;22346</id>
<activity>a3/a4</activity>
<result>r3/r4</result>
<operator>test</operator>
<timestamp>12/18/2014 3:51:19 PM</timestamp>
<quantity>2</quantity>
</rl>
</Report>
and for my xsl,
<table border="1" style="border-width: 1px" width="90%" bordercolor="#C0C0C0" align="center">
<tr>
<th width="5%" align="center">
<font color="#000000" face="Verdana" size="3">Index</font>
</th>
<th width="15%" align="center">
<font color="#000000" face="Verdana" size="3">ID A:</font>
</th>
<th width="15%" align="center">
<font color="#000000" face="Verdana" size="3">ID B:</font>
</th>
</tr>
<xsl:for-each select="Report/rl">
<tr height="25">
<td width="5%" align="center" >
<font color="#000000" face="Verdana" size="2">
<xsl:value-of select="position()" />
</font>
</td>
<td align="center">
<font color="#000000" face="Verdana" size="2">
<xsl:value-of select="idA" />
</font>
</td>
<td align="center">
<font color="#000000" face="Verdana" size="2">
<xsl:value-of select="idB" />
</font>
</td>
</tr>
</xsl:for-each>
</table>
For 1st rl, there is 12345:12346 for tag, I want to split them into 12345 and 12346 and show them in the 'idA' and 'idB'. How should I do that?
My xslt version is 1.0.
Assuming there are always exactly two values, separated by a semicolon, use:
<xsl:value-of select="substring-before(id, ';')"/>
to populate the idA cell, and:
<xsl:value-of select="substring-after(id, ';')"/>
to populate the idB cell.
Added:
For the same example as posted, can you elaborate more about the
'recursive named template'?
The solution using a recursive named template would look something like this:
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="/Report">
<table border="1">
<tr>
<th>Index</th>
<th>ID A</th>
<th>ID B</th>
</tr>
<xsl:apply-templates select="rl"/>
</table>
</xsl:template>
<xsl:template match="rl">
<tr>
<td>
<xsl:value-of select="position()" />
</td>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="id"/>
</xsl:call-template>
</tr>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="';'"/>
<td>
<xsl:value-of select="substring-before(concat($text, $delimiter), $delimiter)"/>
</td>
<xsl:if test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Note that for the header we assume that the number of columns is known beforehand. Otherwise you'd have to use a similar recursive template to generate the header cells too.
Check this example
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*">
<xsl:for-each select="rl">
<node>
<idA>
<xsl:value-of select="substring-before(id,';')"/>
</idA>
<idB>
<xsl:value-of select="substring-after(id,';')"/>
</idB>
</node>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT union of attributes

How can I transform this XML:
<data>
<entry a="2" b="3" />
<entry a="2" c="3" />
<entry b="2" c="3" />
<entry a="1" b="2" c="3" />
</data>
Into a table containing a union of all the attributes of the entrys in the header, with either the values or blanks in the rows:
<table>
<tr><th>a</th><th>b</th><th>c</th></tr>
<tr><td>2</td><td>3</td><td> </td></tr>
<tr><td>2</td><td> </td><td>3</td></tr>
<tr><td> </td><td>2</td><td>3</td></tr>
<tr><td>1</td><td>2</td><td>3</td></tr>
</table>
<xsl:key name="attrs" match="data/entry/#*" use="local-name(.)"/>
<xsl:variable name="unique-attr-names" select="data/entry/#*[generate-id() = generate-id(key('attrs', local-name(.))[1])]" />
<xsl:template match="data">
<table>
<tr><xsl:call-template name="unique-attrs" /></tr>
<xsl:apply-templates />
</table>
</xsl:template>
<xsl:template name="unique-attrs">
<xsl:for-each select="$unique-attr-names">
<xsl:sort data-type="text" order="ascending" />
<th><xsl:value-of select="local-name(.)"/></th>
</xsl:for-each>
</xsl:template>
<xsl:template match="entry">
<xsl:variable name="e" select="." />
<tr>
<xsl:for-each select="$unique-attr-names">
<xsl:sort data-type="text" order="ascending" />
<xsl:variable name="a" select="." />
<th><xsl:value-of select="($e/#*[local-name() = local-name($a)])[1]" /></th>
</xsl:for-each>
</tr>
</xsl:template>
You may also try this (based on muenchian grouping):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" />
<xsl:key name="kEntryAttr" match="entry/#*" use="name()" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="entry">
<xsl:variable name="e" select="." />
<tr>
<xsl:for-each select="//entry/#*[count(. | key('kEntryAttr', name() )[1] ) = 1]" >
<td>
<xsl:value-of select="$e/#*[name() = name(current() )]"/>
</td>
</xsl:for-each>
</tr>
</xsl:template>
<xsl:template match="*">
<table>
<tr>
<xsl:for-each select="//entry/#*[count(. | key('kEntryAttr', name() )[1] ) = 1]" >
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:for-each>
</tr>
<xsl:apply-templates select="entry" />
</table>
</xsl:template>
</xsl:stylesheet>
Which will generate the following output.
<?xml version="1.0"?>
<table>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
<tr>
<td>2</td>
<td>3</td>
<td/>
</tr>
<tr>
<td>2</td>
<td/>
<td>3</td>
</tr>
<tr>
<td/>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>

Sums and Category Grouping and sub Grouping with XSLT

With XSLT, 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="3" />
<element id="4" Team="Astros" Season="2011" Points="12" />
<element id="5" Team="Astros" Season="2011" Points="9" />
<element id="5" Team="Astros" Season="2012" Points="2" />
</root>
Into:
<body>
<h2>Rangers</h2>
<table>
<tr><td>2011</td><td>12</td></tr>
<tr><td>2012</td><td>8</td></tr>
<tr><td>Total</td><td>20</td></tr>
<h2>Astros</h2>
<table>
<tr><td>2011</td><td>21</td></tr>
<tr><td>2012</td><td>2</td></tr>
<tr><td>Total</td><td>1227707</td></tr>
</table>
</body>
I tried this example but it is missing the extra Muenchian grouping required.
Sums and Category Grouping with XSLT
Any help would be greatly appreciated!
This should do it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="kTeam" match="element" use="#Team" />
<xsl:key name="kTeamYear" match="element" use="concat(#Team, '+', #Season)"/>
<xsl:template match="root">
<body>
<xsl:apply-templates select="element[generate-id(.) =
generate-id(key('kTeam',#Team)[1])]"
mode="group"/>
</body>
</xsl:template>
<xsl:template match="element" mode="group">
<xsl:variable name="thisTeam" select="key('kTeam',#Team)" />
<h2>
<xsl:value-of select="#Team" />
</h2>
<table>
<xsl:apply-templates
select="$thisTeam[generate-id() =
generate-id(key('kTeamYear',
concat(#Team, '+', #Season))[1]
)]" />
<tr>
<td>
<xsl:text>Total</xsl:text>
</td>
<td>
<xsl:value-of select="sum($thisTeam/#Points)"/>
</td>
</tr>
</table>
</xsl:template>
<xsl:template match="element">
<xsl:variable name="thisSeason"
select="key('kTeamYear', concat(#Team, '+', #Season))" />
<tr>
<td>
<xsl:value-of select="#Season" />
</td>
<td>
<xsl:value-of select="sum($thisSeason/#Points)" />
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces:
<body>
<h2>Rangers</h2>
<table>
<tr>
<td>2011</td>
<td>12</td>
</tr>
<tr>
<td>2012</td>
<td>8</td>
</tr>
<tr>
<td>Total</td>
<td>20</td>
</tr>
</table>
<h2>Astros</h2>
<table>
<tr>
<td>2011</td>
<td>21</td>
</tr>
<tr>
<td>2012</td>
<td>2</td>
</tr>
<tr>
<td>Total</td>
<td>23</td>
</tr>
</table>
</body>

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>

Matrix transposition in XSLT

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"?>