I stink at XSLT, so I'm not sure how to approach this... I'm getting a feed from the EQ2 database. The XML looks like this:
<guilds limit="1" returned="1">
<guild accounts="3" alignment="0" dateformed="1127855265" guildid="111" guildstatus="0" id="1111111111" last_update="1326986410" level="11" name="MyGuild" version="1" world="Permafrost" worldid="202">
<ranks>
<rank id="0" name="Leader"/>
<rank id="1" name="Senior Officer"/>
<rank id="2" name="Officer"/>
<rank id="3" name="Senior Member"/>
<rank id="4" name="Member"/>
<rank id="5" name="Junior Member"/>
<rank id="6" name="Initiate"/>
<rank id="7" name="Recruit"/>
</ranks>
<members>
<member dbid="123456" rank="0" name="Dude1"/>
<member dbid="123457" rank="1" name="Dude2"/>
<member dbid="123458" rank="2" name="Dude3"/>
<member dbid="123459" rank="4" name="Dude4"/>
<member dbid="123460" rank="4" name="Dude5"/>
<member dbid="123461" rank="4" name="Dude6"/>
</members>
<events/>
</guild>
</guilds>
I'm trying to add the ranks into a table. The XSL (version 1) snippet for it is as follows, but adding the name from the rank isn't working properly - I know this part is wrong:
<xsl:value-of select="rank/rank[#id=1]/#name"/>
So, can I get some help making it work and maybe an idea of how to shorten it?
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="4.0" indent="yes"/>
<xsl:template match="/">
<table width="100%" cellspacing="0" cellpadding="0" id="eq2roster" align="center">
<thead>
<tr>
<td colspan="4" class="eq2CharacterHeader">Character</td>
<td colspan="3" class="eq2TradeskillsHeader">Tradeskills</td>
</tr>
<tr class="ForumCategoryHeader">
<th class="eq2NameHeader">Name</th>
<th class="eq2RankHeader">Rank</th>
<th class="eq2ClassHeader">Class</th>
<th class="eq2LevelHeader">Level</th>
<th class="eq2ArtisanHeader">Artisan</th>
<th class="eq2ArtisanLevelHeader">Level</th>
<th class="eq2SecondaryHeader">Secondary</th>
</tr>
</thead>
<tbody>
<xsl:for-each select="guilds/guild/members/member">
<xsl:if test="#id > 1">
<tr>
<td class="eq2Name">
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:text>http://eq2players.station.sony.com/</xsl:text>
<xsl:value-of select="concat(normalize-space(/guilds/guild/#world), '/')"/>
<xsl:value-of select="concat(normalize-space(#name), '/')"/>
</xsl:attribute>
<xsl:attribute name="target">
<xsl:text>_blank</xsl:text>
</xsl:attribute>
<xsl:value-of select="normalize-space(#name)"/>
</xsl:element>
</td>
<xsl:element name="td">
<xsl:attribute name="class">
<xsl:text>eq2Rank eq2rank-</xsl:text>
<xsl:value-of select="translate(normalize-space(guild/#rank),' ','')"/>
</xsl:attribute>
<xsl:choose>
<xsl:when test="member/#rank = 1"><span class = "rank1"><xsl:value-of select="ranks/rank[#id=1]/#name"/></span></xsl:when>
<xsl:when test="member/#rank = 2"><span class = "rank2"><xsl:value-of select="ranks/rank[#id=2]/#name"/></span></xsl:when>
<xsl:when test="member/#rank = 3"><span class = "rank3"><xsl:value-of select="ranks/rank[#id=3]/#name"/></span></xsl:when>
<xsl:when test="member/#rank = 4"><span class = "rank4"><xsl:value-of select="ranks/rank[#id=4]/#name"/></span></xsl:when>
<xsl:when test="member/#rank = 5"><span class = "rank5"><xsl:value-of select="ranks/rank[#id=5]/#name"/></span></xsl:when>
<xsl:when test="member/#rank = 6"><span class = "rank6"><xsl:value-of select="ranks/rank[#id=6]/#name"/></span></xsl:when>
<xsl:when test="member/#rank = 7"><span class = "rank7"><xsl:value-of select="ranks/rank[#id=7]/#name"/></span></xsl:when>
</xsl:choose>
</xsl:element>
<xsl:element name="td">
<xsl:attribute name="class">
<xsl:text>eq2Class eq2</xsl:text>
<xsl:value-of select="translate(normalize-space(type/#class),' ','')"/>
</xsl:attribute>
<xsl:value-of select="type/#class"/>
</xsl:element>
<td class="eq2level"><xsl:value-of select="type/#level"/></td>
<xsl:element name="td">
<xsl:attribute name="class">
<xsl:text>eq2ArtisanClass eq2</xsl:text>
<xsl:value-of select="translate(normalize-space(tradeskills/tradeskill/#class),' ','')"/>
</xsl:attribute>
<xsl:value-of select="tradeskills/tradeskill/#class"/>
</xsl:element>
<td class="eq2ArtisanLevel"><xsl:value-of select="tradeskills/tradeskill/#level"/></td>
<xsl:element name="td">
<xsl:attribute name="class">
<xsl:text>eq2Secondary eq2</xsl:text>
<xsl:value-of select="translate(normalize-space(secondarytradeskills/secondarytradeskill/#name),' ','')"/>
</xsl:attribute>
<xsl:value-of select="secondarytradeskills/secondarytradeskill/#name"/>
</xsl:element>
</tr>
</xsl:if>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
The following expression associates a member with its corresponding rank using current() (assuming that the context node is the member in question):
../../ranks/rank[#id=current()/#rank]/#name
Full example:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="member">
<td class="eq2Rank eq2rank-{guild/#rank}">
<span class="rank{#rank}">
<xsl:value-of
select="../../ranks/rank[#id=current()/#rank]/#name"/>
</span>
</td>
</xsl:template>
</xsl:stylesheet>
Output (based on the originally posted sample XML):
<td class="eq2Rank eq2rank-">
<span class="rank0">Leader</span>
</td>
<td class="eq2Rank eq2rank-">
<span class="rank1">Senior Officer</span>
</td>
<td class="eq2Rank eq2rank-">
<span class="rank2">Officer</span>
</td>
<td class="eq2Rank eq2rank-">
<span class="rank4">Member</span>
</td>
<td class="eq2Rank eq2rank-">
<span class="rank4">Member</span>
</td>
<td class="eq2Rank eq2rank-">
<span class="rank4">Member</span>
</td>
Not sure if you can do something like this but it might be worth a shot. In the context of a single member
<xsl:value = "//ranks/rank[#id = ./#rank]/#name"/>
It uses X-Path to jump to the top and look for the ranks node, find the rank element whose ID attribute equals the rank attribute of the member, and grabs the name attribute
It's been a few years since I had to XSL like that, but should allow you to get rid of the XSL:choose block
I'm not really sure if I get the question.
Asume that you have the right rankid you probably want to use variable
<xsl:variable name="rank" select="translate(normalize-space(guild/#rank),' ','')"/>
<span class = "rank{$rank}"><xsl:value-of select="ranks/rank[#id = $rank]/#name"/></span>
Related
I have an XML doc with two sets of data in it. I need to output a HTML table that combines the data.
The XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<fdata>
<RepairHistory>
<RepairList>
<RepairJob>
<RepairRef>56740408</RepairRef>
<RepairDescription>Baths</RepairDescription>
<RepairReportedDate>20180515</RepairReportedDate>
<RepairCompletedDate></RepairCompletedDate>
<RepairStartDate>20180515</RepairStartDate>
</RepairJob>
<RepairJob>
<RepairRef>56735043</RepairRef>
<RepairDescription>Basins</RepairDescription>
<RepairReportedDate>20180515</RepairReportedDate>
<RepairCompletedDate></RepairCompletedDate>
<RepairStartDate>20180515</RepairStartDate>
</RepairJob>
<RepairJob>
<RepairRef>56740415</RepairRef>
<RepairDescription>Showers</RepairDescription>
<RepairReportedDate>20180515</RepairReportedDate>
<RepairCompletedDate></RepairCompletedDate>
<RepairStartDate>20180515</RepairStartDate>
</RepairJob>
</RepairList>
</RepairHistory>
<map>
<entry>
<string>GUID</string>
<string>be0f53f5-7a09-47cd-9928-0c865b6450a5</string>
</entry>
<entry>
<string>JOBNUMBER</string>
<string>56740408</string>
</entry>
</map>
<map>
<entry>
<string>GUID</string>
<string>5ce2e8fe-7735-4f98-b3a9-3bd386edb338</string>
</entry>
<entry>
<string>JOBNUMBER</string>
<string>56740415</string>
</entry>
</map>
</fdata>
JOBNUMBER has to match RepairRef. Not all of the data at the bottom matches the data at the top.
I need to output the data in a table as follows:
Date Ref Details GUID
20180515 56740408 Baths be0f53f5-7a09-47cd-9928-0c865b6450a5
20180515 56735043 Basins No GUID
20180515 56740415 Showers 5ce2e8fe-7735-4f98-b3a9-3bd386edb338
I have tried to output the table using the following xsl, but it doesn't work...
<xsl:template match="/">
<h3>Repair history</h3>
<table>
<tr>
<th>Date</th>
<th>Ref</th>
<th>Details</th>
<th>GUID</th>
</tr>
<xsl:apply-templates select ="//RepairJob" />
</table>
</xsl:template>
<xsl:template match="RepairJob">
<xsl:variable name="JobNumber" select="RepairRef" />
<tr>
<td><xsl:value-of select="RepairStartDate"/></td>
<td><xsl:value-of select="RepairRef"/></td>
<td><xsl:value-of select="RepairDescription"/></td>
<td>
<xsl:choose>
<xsl:when test="count(//map//entry[2]//string[2] = $JobNumber) > 0">
<xsl:value-of select="//map//entry[2]//string[2] = $JobNumber"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>No GUID</xsl:text>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
I can see that I am not selecting the correct node to display, but the matching is not working either. Can anyone help?
Your current xsl:value-of evaluates to a boolean ("true" or "false"). The expression you actually want is this...
<xsl:value-of select="//map[entry[2]/string[2] = $JobNumber]/entry[1]/string[2]"/>
The xsl:when needs a tweak too, so the whole xsl:choose should look like this:
<xsl:choose>
<xsl:when test="//map[entry[2]/string[2] = $JobNumber]">
<xsl:value-of select="//map[entry[2]/string[2] = $JobNumber]/entry[1]/string[2]"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>No GUID</xsl:text>
</xsl:otherwise>
</xsl:choose>
However, consider using a key to look up the guids....
<xsl:key name="map" match="map" use="entry[string[1] = 'JOBNUMBER']/string[2]" />
Then your xsl:value-of becomes this
<xsl:value-of select="key('map',$JobNumber)/entry[string[1] = 'GUID']/string[2]"/>
Note, this also allows for the entry elements to be in any order within a map element (i.e. the JOBNUMBER could be the first entry, and the GUID the second)
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:key name="map" match="map" use="entry[string[1] = 'JOBNUMBER']/string[2]" />
<xsl:template match="/">
<h3>Repair history</h3>
<table>
<tr>
<th>Date</th>
<th>Ref</th>
<th>Details</th>
<th>GUID</th>
</tr>
<xsl:apply-templates select ="//RepairJob" />
</table>
</xsl:template>
<xsl:template match="RepairJob">
<xsl:variable name="JobNumber" select="RepairRef" />
<tr>
<td><xsl:value-of select="RepairStartDate"/></td>
<td><xsl:value-of select="RepairRef"/></td>
<td><xsl:value-of select="RepairDescription"/></td>
<td>
<xsl:choose>
<xsl:when test="key('map',$JobNumber)">
<xsl:value-of select="key('map',$JobNumber)/entry[string[1] = 'GUID']/string[2]"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>No GUID</xsl:text>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
I'm using the following xml information:
<section>
<...>
</section>
<section>
<templateId root="2.16.840.1.113883.10.20.22.2.10" />
<text>
<table id="Appointments">
<tr>
<td id="heading">Appointments</td>
</tr>
<tr>
<td id="content">No future appointments scheduled.</td>
</tr>
</table>
<br />
<table id="Referrals">
<tr>
<td id="heading">Referrals</td>
</tr>
<tr>
<td id="content">No referrals available.</td>
</tr>
</table>
<br />
</text>
<section>
<section>
<...>
</section>
There are multiple section nodes (with their own child nodes, including templateId) within the document. I'm having trouble with this one so I wanted to be specific in the xml information.
and in my xslt file I want to get one particular table out. I'm referencing it the following way (I'm trying to use templates and I'm new to XSL so please bear with me)
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//section[templateId/#root='2.16.840.1.113883.10.20.22.2.17']/text/table[#id='Appointments']" />
</xsl: template>
<xsl:template match="section[templateId/#root='2.16.840.1.113883.10.20.22.2.17']/text/table[#id='Appointments']">
<div style="float: left; width: 50%;">
<span style="font-weight: bold;">
<xsl:value-of select="tr/td[#id='heading']"/>:
</span>
<br />
<xsl:call-template name="replace">
<xsl:with-param name="string" select="tr/td[#id='content']"/>
</xsl:call-template>
</div>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="string"/>
<xsl:choose>
<xsl:when test="contains($string,'
')">
<xsl:value-of select="substring-before($string,'
')"/>
<br/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
In this particular xml example, the output should be:
Appointments:
No future appointments scheduled.
I'm thinking the match and select need some tweaking but not sure what part.
Also, if the template can be tweaked so that I could pass a parameter with the table/#id value so that I could reuse this one template for a couple of items,that would be even more beneficial (the output for referrals and appointments that are in this example would be the same).
Thanks for any help
This is your XML section root attribute (cut and paste from your XML):
root="2.16.840.1.113883.10.20.22.2.10"
This is your test XSL:
root='2.16.840.1.113883.10.20.22.2.17'
Of course they do not match, one ends with "10", the other with "17"
Changing the data to "17" and correcting the other errors in my comments yields:
<div style="float: left; width: 50%;"><span style="font-weight: bold;">Appointments:
</span><br>No future appointments scheduled.
</div
I'm using XSL1.0. My editor/debugger is OxygenXML with Saxon (OxygenXML can't debug with MSXML) and it will deployed to work with a 3rd party app that only uses MSXML. This means I can't use a variable containing a nodeset if I want to be able to debug.
The problem could probably be expressed as how to sequentially number output of the following -
<xsl:for-each select="node1">
<xsl:variable name="current_ID" select="ID">
<xsl:for-each select="sub_node1">
<xsl:value-of select="../ID"/>-<xsl:value-of select="Sub_ID"/>
</xsl:for-each>
</xsl:for-each>
understanding that I cannot simply use this in my scenario:
<xsl:for-each select="node1/sub_node1">
<xsl:value-of select="position()"/>
</xsl:for-each>
Below is a manufactured example that shows the problem I'm trying to solve as part of a much larger XSL/XML combo. I basically need to create manufacturing instructions. All nodes in the XML with the exception of products/versions (by qty) are in the correct order and I cannot change it. I need to generate the same set of sequential numbers from 3 different XSL's. My current context will always be shipments/deliveries/delivery_products (i.e. my XSL has to process the nodes in the seq shown). I need to produce a list of products sorted by version qty and their deliveries. Each row should have a sequential no (1-4) in example below
<shipments>
<product>
<name>Product 1</name>
<prod_id>P1</prod_id>
<version>
<version_id>P1_V1</version_id>
<qty>8800</qty>
</version>
<version>
<version_id>P1_V2</version_id>
<qty>1100</qty>
</version>
<version>
<version_id>P1_V3</version_id>
<qty>100</qty>
</version>
</product>
<product>
<name>Product 2</name>
<prod_id>P2</prod_id>
<version>
<version_id>P2_V1</version_id>
<qty>5000</qty>
</version>
<version>
<version_id>P2_V2</version_id>
<qty>5000</qty>
</version>
<version>
<version_id>P2_V3</version_id>
<qty>2000</qty>
</version>
</product>
<deliveries>
<del_id>1</del_id>
<destination>Miami</destination>
<delivery_products>
<version_id>P1_V1</version_id>
<qty>8000</qty>
</delivery_products>
<delivery_products>
<version_id>P2_V1</version_id>
<qty>5000</qty>
</delivery_products>
</deliveries>
<deliveries>
<del_id>2</del_id>
<destination>New York</destination>
<delivery_products>
<version_id>P1_V1</version_id>
<qty>800</qty>
</delivery_products>
<delivery_products>
<version_id>P2_V2</version_id>
<qty>1000</qty>
</delivery_products>
</deliveries>
Expected output is below. Note seq # starts from 1 and counts up to 4
<table>
<thead>
<tr>
<td class="col_head">
Seq
</td>
<td class="col_head">
Version
</td>
<td class="col_head">
Destination
</td>
<td class="col_head">
Qty
</td>
</tr>
</thead>
<tr>
<td colspan="4" class="rev_heading">Product 1</td>
</tr>
<tr>
<td>1</td>
<td>P1_V1</td>
<td>Miami</td>
<td>8000</td>
</tr>
<tr>
<td>2</td>
<td>P1_V1</td>
<td>New York</td>
<td>800</td>
</tr>
<tr>
<td colspan="4" class="rev_heading">Product 2</td>
</tr>
<tr>
<td>3</td>
<td>P2_V1</td>
<td>Miami</td>
<td>5000</td>
</tr>
<tr>
<td>4</td>
<td>P2_V2</td>
<td>New York</td>
<td>5000</td>
</tr>
</table>
Here's my XSL so far (just stuck a position() in for a place holder for the seq #)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="html" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<xsl:template match="shipments">
<html>
<head>
<title>Seq Test</title>
<style type="text/css">
table {border: 1px solid black; border-collapse: collapse;}
td {border: 1px solid black; padding: 1px 5px 1px 5px;}
.col_head {font-weight: 600;}
.rev_heading {color: red; text-align: center; padding-top: 15px;}
</style>
</head>
<body>
<table>
<thead>
<tr>
<!-- SEQ# -->
<td class="col_head">
Seq
</td>
<!-- Imprint/Version -->
<td class="col_head">
Version
</td>
<!-- Ship to -->
<td class="col_head">
Destination
</td>
<!-- Qty -->
<td class="col_head">
Qty
</td>
</tr>
</thead>
<xsl:for-each select="product">
<xsl:sort data-type="number" select="qty"/>
<xsl:for-each select="version">
<xsl:variable name="curr_version" select="version_id"/>
<xsl:if test="position() = 1">
<tr>
<td colspan="4" class="rev_heading">
<xsl:value-of select="../name"/>
</td>
</tr>
</xsl:if>
<xsl:for-each select="../../deliveries/delivery_products[version_id = $curr_version]">
<tr >
<!-- SEQ# -->
<td>
<xsl:value-of select="position()"/>
</td>
<!-- Version -->
<td>
<xsl:value-of select="version_id"/>
</td>
<!-- Ship to -->
<td>
<xsl:value-of select="../destination"/>
</td>
<!-- QTY -->
<td>
<xsl:value-of select="qty"/>
</td>
</tr>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I'm using XSL1.0. My editor/debugger is OxygenXML with Saxon
(OxygenXML can't debug with MSXML) and it will deployed to work with a
3rd party app that only uses MSXML. This means I can't use a variable
containing a nodeset if I want to be able to debug.
You can still use oXygen and the EXSLT node-set() function.
When you are finished, simply change the namespace-uri from "http://exslt.org/common" to "urn:schemas-microsoft-com:xslt"
Here is a short example of this technique. Suppose you are finished debugging the below transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="num[. mod 3 = 0]"/>
</xsl:variable>
<xsl:copy-of select="sum(ext:node-set($vrtfPass1)/*)"/>
</xsl:template>
<xsl:template match="num">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Then you make the change from the EXSLT namespace-uri to the MSXSL namespace uri:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="urn:schemas-microsoft-com:xslt">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="num[. mod 3 = 0]"/>
</xsl:variable>
<xsl:copy-of select="sum(ext:node-set($vrtfPass1)/*)"/>
</xsl:template>
<xsl:template match="num">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Finally, you run this last transformation with MSXML and it produces exactly the same result as the initial transformation that uses EXSLT:
18
I have been trying to figure out why my variable assigned sum isn't working, also my table output is repeating only the first element through out the whole table. What i am trying to get this code to do is get the studentID of each student printed in the first column, the student name of that ID in the second column, and then assign a variable that holds the students total mark of the 3 assessments they have completed to print their total mark in the third column, followed by as assignment of HD, D, C, P, or F as based on their total mark e.g. HD is 85 plus and D is 75+ but not higher than 84. etc.
Can someone tell me where I am going wrong? I am still new to XML/XSL so criticism is welcome.
grade.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="/">
<xsl:variable name="StudentAmount" select="count(document('AssessmentItems.xml')/assessmentList/unit/studentList/student)"/>
<xsl:variable name="totalmark" select="sum(document('AssessmentItems.xml')/assessmentList/unit/*
[//assessmentList/unit/assessmentItems/assessment/#studId = //assessmentList/unit/studentList/student/#sid])"/>
<html>
<body>
<h2>Grade Report for <xsl:value-of select="assessmentList/unit/#unitId"/> - <xsl:value-of select="assessmentList/unit/unitName"/></h2>
<p>Number of students in this unit: <xsl:value-of select="$StudentAmount"/></p>
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Total Mark</th>
<th>Grade</th>
</tr>
<xsl:for-each select="assessmentList/unit/studentList/student">
<tr>
<td><xsl:value-of select="document('AssessmentItems.xml')/assessmentList/unit/studentList/student/#sid"/></td>
<td><xsl:value-of select="document('AssessmentItems.xml')/assessmentList/unit/studentList/student"/></td>
<td><xsl:value-of select="document('AssessmentItems.xml')/assessmentList/unit/assessmentItems/assessment/mark"/></td>
<xsl:choose>
<xsl:when test="$totalmark > 85">
<td color="blue">HD</td>
</xsl:when>
<xsl:when test="$totalmark > 75">
<td color="black">D</td>
</xsl:when>
<xsl:when test="$totalmark > 65">
<td color="black">C</td>
</xsl:when>
<xsl:when test="$totalmark > 50">
<td color="black">P</td>
</xsl:when>
<xsl:otherwise>
<td color="red">F</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
and this is the file AssessmentItems.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="grade.xsl"?>
<assessmentList>
<unit unitId="3311">
<unitName>Learn To Read</unitName>
<studentList>
<student sid="1001">Lisa Simpson</student>
<student sid="1002">Barney Rubble</student>
<student sid="1003">Donald Duck</student>
</studentList>
<assessmentItems>
<assessment name="Assignment 1" weight="20">
<mark studId="1001">12</mark>
<mark studId="1002">18</mark>
<mark studId="1003">9</mark>
</assessment>
<assessment name="Assignment 2" weight="25">
<mark studId="1001">23</mark>
<mark studId="1002">14</mark>
<mark studId="1003">12.5</mark>
</assessment>
<assessment name="Quiz" weight="15">
<mark studId="1001">13</mark>
<mark studId="1002">9</mark>
<mark studId="1003">6</mark>
</assessment>
<assessment name="Final Exam" weight="40">
<mark studId="1001">38</mark>
<mark studId="1002">21</mark>
<mark studId="1003">20.5</mark>
</assessment>
</assessmentItems>
</unit>
</assessmentList>
Firstly, because you are only working on a single XML document, you don't need the constant references to document('AssessmentItems.xml') at all. So, for example
<xsl:value-of
select="document('AssessmentItems.xml')/assessmentList/unit/studentList/student/#sid"/>
Can be replaced with just
<xsl:value-of select="/assessmentList/unit/studentList/student/#sid"/>
This leads on to the second problem. The xpath above is relative to the document element of the XML and will return the #sid of the very first student it finds, and no the #sid of the student you are currently positioned on. You can simply do this in your case
<xsl:value-of select="#sid"/>
Another issue is that you define the variable totalmarks at the top of the XSLT, when in fact it should be defined within the scope of your xsl:for-each so that it is specific for the current student
<xsl:variable name="totalmark" select="sum(../../assessmentItems/assessment/mark[#studId = current()/#sid])" />
Actually, it may be better to make use of a key here, to look up the results
<xsl:key name="marks" match="mark" use="#studId" />
And to get the total results for a student....
<xsl:variable name="totalmark" select="sum(key('marks', #sid))" />
One final comment, although not a problem, it is often better to use xsl:apply-templates rather than xsl:for-each as this avoids excessive indentation, and allows better code re-use.
Try the following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="marks" match="mark" use="#studId"/>
<xsl:template match="/">
<xsl:variable name="StudentAmount" select="count(/assessmentList/unit/studentList/student)"/>
<html>
<body>
<h2>Grade Report for
<xsl:value-of select="assessmentList/unit/#unitId"/>-
<xsl:value-of select="assessmentList/unit/unitName"/>
</h2>
<p>Number of students in this unit:
<xsl:value-of select="$StudentAmount"/></p>
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Total Mark</th>
<th>Grade</th>
</tr>
<xsl:apply-templates select="assessmentList/unit/studentList/student"/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="student">
<xsl:variable name="totalmark" select="sum(key('marks', #sid))"/>
<tr>
<td>
<xsl:value-of select="#sid"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
<td>
<xsl:value-of select="$totalmark"/>
</td>
<xsl:choose>
<xsl:when test="$totalmark > 85">
<td color="blue">HD</td>
</xsl:when>
<xsl:when test="$totalmark > 75">
<td color="black">D</td>
</xsl:when>
<xsl:when test="$totalmark > 65">
<td color="black">C</td>
</xsl:when>
<xsl:when test="$totalmark > 50">
<td color="black">P</td>
</xsl:when>
<xsl:otherwise>
<td color="red">F</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following HTML is output
<html>
<body>
<h2>Grade Report for 3311- Learn To Read</h2>
<p>Number of students in this unit: 3</p>
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Total Mark</th>
<th>Grade</th>
</tr>
<tr>
<td>1001</td>
<td>Lisa Simpson</td>
<td>86</td>
<td color="blue">HD</td>
</tr>
<tr>
<td>1002</td>
<td>Barney Rubble</td>
<td>62</td>
<td color="black">P</td>
</tr>
<tr>
<td>1003</td>
<td>Donald Duck</td>
<td>48</td>
<td color="red">F</td>
</tr>
</table>
</body>
</html>
Do note this assumes only one unit element in your XML. If your actual XML have multiple units, and you wanted a separate table for each, then this is not a problem, you would just need to make sure the unit id is part of the xsl:key so you can look up results for a given student in a given unit.
A very quick glance at your code reveals that the predicate
[//assessmentList/unit/assessmentItems/assessment/#studId = //assessmentList/unit/studentList/student/#sid]
is obviously wrong, because it has the same value (either true or false) for every element in your source document.
Correcting it requires more study of the problem than I have time for. But you seem to have fallen victim to the "if it doesn't work then put '//' at the front" fallacy.
The source XML (this is just foobar data, in reality it is thousands of rows wich can be both positive and negative):
<accounting>
<entry id="1">
<accounting_date>2010-10-29</accounting_date>
<transfer_date>2010-10-29</transfer_date>
<description>Start balance</description>
<vat>0</vat>
<sum>87287</sum>
</entry>
<entry id="2">
<accounting_date>2011-01-24</accounting_date>
<transfer_date>2011-02-17</transfer_date>
<description>Bill 1</description>
<vat>175</vat>
<sum>875</sum>
</entry>
<entry id="3">
<accounting_date>2011-01-31</accounting_date>
<transfer_date>2011-01-18</transfer_date>
<description>Bill 2</description>
<vat>350</vat>
<sum>1750</sum>
</entry>
</accounting>
I want to transform this XML to an HTML table to display to the user. Most of the transformation is just putting values in the right places, but the balance-field is giving me headache.
My XSLT (that does not work):
<table>
<tr>
<th>Accounting date</th>
<th>Description</th>
<th>Sum</th>
<th>Balanche</th>
</tr>
<xsl:for-each select="/accounting/entry">
<tr>
<td><xsl:value-of select="accounting_date" /></td>
<td><xsl:value-of select="description" /></td>
<td><xsl:value-of select="sum" /></td>
<td><xsl:value-of select="sum(../entry[position() < current()/position()]/sum)" /></td><!-- This XPath is the problem! -->
</tr>
</xsl:for-each>
</table>
Expected result:
<table>
<tr>
<th>Accounting date</th>
<th>Description</th>
<th>Sum</th>
<th>Balanche</th>
</tr>
<tr>
<td>2010-10-29</td>
<td>Start balance</td>
<td>87287</td>
<td>87287</td>
</tr>
<tr>
<td>2011-01-24</td>
<td>Bill 1</td>
<td>875</td>
<td>88162</td>
</tr>
<tr>
<td>2011-01-31</td>
<td>Bill 2</td>
<td>1750</td>
<td>89912</td>
</tr>
</table>
Chrome is blank, and Firefox gives me:
Error loading stylesheet: XPath parse failure: Name or Nodetype test expected:
I'm stuck, please help. :)
The best solution might depend a bit on whether you are using XSLT 1.0 or XSLT 2.0. You really need to say, since at present there's a roughly even mix of both in use in the field. (It seems you're running it in the browser, which suggests you want a 1.0 solution, so that's what I've given you).
But either way, recursion is your friend. In this case, "sibling recursion" where you write a template to process an entry, and it does apply-templates to process the next entry, passing the total so far as a parameter: something like this
<xsl:template match="entry">
<xsl:param name="total-so-far" select="0"/>
<tr>
<td><xsl:value-of select="accounting_date" /></td>
<td><xsl:value-of select="description" /></td>
<td><xsl:value-of select="sum" /></td>
<td><xsl:value-of select="$total-so-far + sum"/></td><
</tr>
<xsl:apply-templates select="following-sibling::entry[1]">
<xsl:with-param name="total-so-far" select="$total-so-far + sum"/>
</xsl:apply-templates>
</xsl:template>
Then you need to start the process off with
<xsl:template match="accounting">
<table>
<xsl:apply-templates select="entry[1]"/>
</table>
</xsl:template>
If there are thousands of rows then this could cause stack overflow in an XSLT processor that doesn't do tail call optimisation. I've no idea whether the XSLT processors in today's browsers implement this optimisation or not.
Alternatively you can use the preceding-sibling axes
<table>
<tr>
<th>Accounting date</th>
<th>Description</th>
<th>Sum</th>
<th>Balanche</th>
</tr>
<xsl:for-each select="/accounting/entry">
<tr>
<td>
<xsl:value-of select="accounting_date" />
</td>
<td>
<xsl:value-of select="description" />
</td>
<td>
<xsl:value-of select="sum" />
</td>
<td>
<xsl:value-of select="sum(preceding-sibling::*/sum)+sum" />
</td>
</tr>
</xsl:for-each>
</table>
In addition to the correct answer by #Michael Kay, here is a general template/function from FXSL to use for computing running totals. Its DVC variant will never (for practical purposes) crash due to stack overflow. With DVC (Divide and Conquer) recursion, processing a sequence of 1000000 (1M) items requires maximum stack depth of only 19.
Here is an example of using the scanl template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
<xsl:import href="scanlDVC.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<myAdd:myAdd/>
<myParam:myParam>0</myParam:myParam>
<xsl:template match="/">
<xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
<xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>
<xsl:call-template name="scanl">
<xsl:with-param name="pFun" select="$vFun"/>
<xsl:with-param name="pQ0" select="$vZero" />
<xsl:with-param name="pList" select="/*/num"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="myAdd:*" mode="f:FXSL">
<xsl:param name="pArg1" select="0"/>
<xsl:param name="pArg2" select="0"/>
<xsl:value-of select="$pArg1 + $pArg2"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML file:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the correct result (running totals) is produced:
<el>0</el>
<el>1</el>
<el>3</el>
<el>6</el>
<el>10</el>
<el>15</el>
<el>21</el>
<el>28</el>
<el>36</el>
<el>45</el>
<el>55</el>
Using it for the provided XML document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
<xsl:import href="scanlDVC.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<myAdd:myAdd/>
<myParam:myParam>0</myParam:myParam>
<xsl:template match="/">
<xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
<xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>
<xsl:call-template name="scanl">
<xsl:with-param name="pFun" select="$vFun"/>
<xsl:with-param name="pQ0" select="$vZero" />
<xsl:with-param name="pList" select="/*/*/sum"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="myAdd:*" mode="f:FXSL">
<xsl:param name="pArg1" select="0"/>
<xsl:param name="pArg2" select="0"/>
<xsl:value-of select="$pArg1 + $pArg2"/>
</xsl:template>
</xsl:stylesheet>
and the correct result is produced::
<el>0</el>
<el>87287</el>
<el>88162</el>
<el>89912</el>