Sums and Category Grouping and sub Grouping with XSLT - 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>

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 count based on Filtered node

I have the following xml:
<Activity>
<item>
<task>XXX</task>
<assignto>User1</assignto>
</item>
<item>
<task>YYY</task>
<assignto>User2</assignto>
</item>
<item>
<task>ZZZ</task>
<assignto>User1</assignto>
</item>
<team>
<member>User1</member>
<member>User2</member>
<team>
</Activity>
I want to generate using XSL a count of task per member in the team.
User- Count
user1- 2
user2- 1
so far I have 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>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(/Activity/item[assignto='user1'])" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
so far I hardcoded 'user1', I would like to filter based on the current member in the for each loop.
Can someone help, please?
Thanks,
here you go, store the member in a variable and test on that variable. You also have an error in your source XML, you need /team.
<xsl:template match="/">
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<xsl:variable name="assignto">
<xsl:value-of select="."/>
</xsl:variable>
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(/Activity/item[assignto=$assignto])" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
output is:
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<tr>
<td>User1</td>
<td>2</td>
</tr>
<tr>
<td>User2</td>
<td>1</td>
</tr>
</table>
</body>
</html>
Just replace your reference to 'user1' with a reference to the current node at the start of the XPath address, which is done using the current() function:
t:\ftemp>type activity.xml
<Activity>
<item>
<task>XXX</task>
<assignto>User1</assignto>
</item>
<item>
<task>YYY</task>
<assignto>User2</assignto>
</item>
<item>
<task>ZZZ</task>
<assignto>User1</assignto>
</item>
<team>
<member>User1</member>
<member>User2</member>
</team>
</Activity>
t:\ftemp>call xslt activity.xml activity.xsl
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<tr>
<td>User1</td>
<td>2</td>
</tr>
<tr>
<td>User2</td>
<td>1</td>
</tr>
</table>
</body>
</html>
t:\ftemp>type activity.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>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(/Activity/item[assignto=current()])" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem Done!
If you want to filter based on the current member, you can just use the "current()" function:
<xsl:value-of select="count(/Activity/item[assignto=current()])" />
However, you might benefit from using a key here, to make the counting more efficient. First define your key like so:
<xsl:key name="item" match="item" use="assignto" />
Then you can write your count like so:
<xsl:value-of select="count(key('item', .))" />
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item" match="item" use="assignto" />
<xsl:template match="/">
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(key('item', .))" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</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>

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 Format data in a table some columns are blank

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>