Alternate Row Color of Table using XSLT - xslt

I have an XSLT where I want to alternate the row colors of an output table. I know that you can use the code such as in this example:
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<xsl:for-each select="//Book">
<xsl:variable name="altColor">
<xsl:choose>
<xsl:when test="position() mod 2 = 0">#FFFFFF</xsl:when>
<xsl:otherwise>#D3DFEE</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr bgcolor="{$altColor}">
<td><xsl:value-of select="current()/#name"/></td>
<td><xsl:value-of select="current()/#ID"/></td>
</tr>
</xsl:for-each>
</table>
which works fine however, I have a few instances where I need to include some if statements within the for-each, such as.
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<xsl:for-each select="//Book">
<xsl:variable name="altColor">
<xsl:choose>
<xsl:when test="position() mod 2 = 0">#FFFFFF</xsl:when>
<xsl:otherwise>#D3DFEE</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="current()/#ID > 10000 and current/#ID < 6000">
<tr bgcolor="{$altColor}">
<td><xsl:value-of select="current()/#name"/></td>
<td><xsl:value-of select="current()/#ID"/></td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
Then it doesn't work because it may skip an item at a position in the for-each and I end up with randomly alternating row colors or it may start at an incorrect position where the rows alternate starting with the incorrect color.
I've tried adding an xsl:sort, which doesn't really fix the problem. Is there a way to avoid this snag?

Try with the following code:
tr[position() mod 2 =0]
<xsl:template match="n1:tr[position() mod 2 =0]">
<tr bgcolor="#aaaaaa">
<xsl:apply-templates/>
</tr>
</xsl:template>
<xsl:template match="n1:tr[position() mod 2 =1]">
<tr bgcolor="#aaaaff">
<xsl:apply-templates/>
</tr>
</xsl:template>

The simplest solution (taking your sample stylesheet as representative of your actual needs) is to only loop over the desired nodes in the first place. Like this:
<xsl:template match="/">
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<xsl:for-each select="//Book[#ID < 10000 and #ID > 6000]">
<xsl:variable name="altColor">
<xsl:choose>
<xsl:when test="position() mod 2 = 0">#FFFFFF</xsl:when>
<xsl:otherwise>#D3DFEE</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr bgcolor="{$altColor}">
<td><xsl:value-of select="#name" /></td>
<td><xsl:value-of select="#ID" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
In other words, instead of conditionally including nodes in the body of the for-each simply select only those nodes you need. By doing this, position() will be relative to the set that you're iterating and it will work as expected.
For example, this (simplified) input:
<r>
<Book ID="6200"/>
<Book ID="7100"/>
<Book/>
<Book/>
<Book ID="8000"/>
<Book/>
<Book ID="9001"/>
<Book ID="9002"/>
</r>
Produces the correct alternation:
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<tr bgcolor="#D3DFEE">
<td />
<td>6200</td>
</tr>
<tr bgcolor="#FFFFFF">
<td />
<td>7100</td>
</tr>
<tr bgcolor="#D3DFEE">
<td />
<td>8000</td>
</tr>
<tr bgcolor="#FFFFFF">
<td />
<td>9001</td>
</tr>
<tr bgcolor="#D3DFEE">
<td />
<td>9002</td>
</tr>
</table>

one simple solution would be to remember last color used and than use opposing one
instead basing your choice on position() and also you should move choice inside test for row
here is pseudo code
currentColor = color1
for each
if ( id > 10000 and id < 6000 ) {
if ( currentColor == color1 )
currentColor= color2
else
currentColor = color1
showDataInColor(currentColor)
}

Related

XSLT: Count the iteration of a cases inside a loop

I have two loops and want to count the iteration of cases OK and NotOK and overall cases inside xslt file.
How I cold do so? If I want to know the sum of both iteration how could I write it?
my For loops are as:
<xsl:for-each select="test">
<xsl:variable name="LinkName" select="attribute::name"/>
<tr>
<th style="text-align:left;vertical-align:top;position:"><a name="{$LinkName}"><xsl:value-of select="$LinkName"/></a></th>
<xsl:for-each select="descendant::node()">
<xsl:choose>
<xsl:when test="attribute::state='NotOK'">
<tr>
<td bgcolor="red"><xsl:value-of select="description"/></td>
</tr>
</xsl:when>
<xsl:when test="attribute::state='OK'">
<tr>
<td bgcolor="lime"><xsl:value-of select="description"/></td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</tr>
</xsl:for-each>
Update:
<table>
<tr bgcolor="coral">
<th>Test cases</th>
<th>Info</th>
</tr>
<xsl:for-each select="test">
<xsl:variable name="Summation"/>
<xsl:variable name="LinkIt" select="#name"/>
<xsl:choose>
<xsl:when test="descendant::node()/#state='NotOK'">
<tr>
<td bgcolor="red"><xsl:value-of select="$LinkIt"/></td>
<td>
<xsl:value-of select="count(descendant::node()[#state='NotOK'])"/> of <xsl:value-of select="count(descendant::node()[#state='OK']) + count(descendant::node()[#state='NotOK'])"/>
</td>
</tr>
</xsl:when>
<xsl:when test="descendant::node()/attribute::state='OK'">
<tr>
<td bgcolor="lime"><xsl:value-of select="$LinkIt"/></td>
<td>
---
</td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</table>
for-each is not a loop. If you want to count all descendant nodes of the test elements then simply use <xsl:value-of select="count(test/descendant::node())"/>. You can also add predicates to XPath expressions, like count(test/descendant::node()[attribute::state='NotOK']).
Inside of a <xsl:for-each select="test"> the context node is a test element so any count expression should be relative to that e.g. count(descendant::node()[attribute::state='NotOK']).

XSL Repeat Template Object containing List<Object>

I have a Library object that contains a collection of Books... The Library object has properties like Name, Address, Phone... While the Book object has properties like ISDN, Title, Author, and Price.
XML looks something like this...
<Library>
<Name>Metro Library</Name>
<Address>1 Post Rd. Brooklyn, NY 11218</Address>
<Phone>800 976-7070</Phone>
<Books>
<Book>
<ISDN>123456789</ISDN>
<Title>Fishing with Luke</Title>
<Author>Luke Miller</Author>
<Price>18.99</Price>
</Book>
<Book>
<ISDN>234567890</ISDN>
<Title>Hunting with Paul</Title>
<Author>Paul Worthington</Author>
<Price>28.99</Price>
</Book>
...
And more books
...
</Books>
</Library>
I have a template with space for only 10 per page for example. There can be hundreds of books in the list of Books... So I need to limit the number of books and repeat the template every 10 books.
<?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>
<div>
<table>
<tr>
<td>NAME</td>
<td><xsl:value-of select="/Library/Name"/></td>
</tr>
<tr>
<td>ADDRESS</td>
<td><xsl:value-of select="/Library/Address"/></td>
</tr>
<tr>
<td>PHONE</td>
<td><xsl:value-of select="/Library/Phone"/></td>
</tr>
</table>
<table>
<xsl:for-each select="/Library/Books/Book">
<tr>
<td><xsl:value-of select="position()"/></td>
<td><xsl:value-of select="ISDN"/></td>
<td><xsl:value-of select="Title"/></td>
<td><xsl:value-of select="Author"/></td>
<td><xsl:value-of select="Price"/></td>
</tr>
</xsl:for-each>
</table>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
How can I get the Library information to appear on all repeating pages and add 10 books per page?... First page has Library info with Books 1 thru 10, Second page has Library info with Books 11 thru 20, and so on??
Thanks
For starters, try not to use for-each, apply-templates allows the engine to optimise the order that events are processed.
It appears that you are calling this stylesheet from some other system, so the approach I've taken is to define a pagination param. In the host language, when you call this just change the root parameter. This then allows you to select the require pages in this line here:
Books/Book[($page - 1)*10 < position() and position() <= ($page)*10]
This should do the trick.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="page" select="1"/>
<xsl:template match="/Library">
<html>
<body>
<div>
<table>
<tr>
<td>NAME</td>
<td>
<xsl:value-of select="/Name"/>
</td>
</tr>
<tr>
<td>ADDRESS</td>
<td>
<xsl:value-of select="/Address"/>
</td>
</tr>
<tr>
<td>PHONE</td>
<td>
<xsl:value-of select="/Phone"/>
</td>
</tr>
</table>
<table>
<xsl:apply-templates select="Books/Book[($page - 1)*10 < position() and position() <= ($page)*10]"/>
</table>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="/Book">
<tr>
<td>
<xsl:value-of select="position()"/>
</td>
<td>
<xsl:value-of select="ISDN"/>
</td>
<td>
<xsl:value-of select="Title"/>
</td>
<td>
<xsl:value-of select="Author"/>
</td>
<td>
<xsl:value-of select="Price"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>

XSLT foreach loop

I have only been introduced to xslt and xml in the last few weeks, and I urgently need some help to write some code to achieve what I want to achieve.
I have the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<abc1 formName="Form">
<Level1>
<Element1>ZZZ</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Pears</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Milkshakes</SubElement5>
</Element2>
</Level1>
<Level1>
<Element1>XXX</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Kiwifruit</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Soda</SubElement5>
</Element2>
</Level1>
</abc1>
and the following html table:
<table>
<tr>
<td width="180" > Row 1</td>
<td width="540" colspan="4"> Cell_A</td>
</tr>
<tr>
<td width="180" >Types</td>
<td width="180" >Type 1</td>
<td width="180" >Type 2</td>
<td width="180" >Type 3</td>
</tr>
<tr>
<td width="180" >Row 2</td>
<td width="180" >Cell_B</td>
<td width="180" >Cell_C</td>
<td width="180" >Cell_D</td>
</tr>
<tr>
<td width="180" > Row 3</td>
<td width="180" >Cell_E</td>
<td width="180" >Cell_F</td>
<td width="180" >Cell_G</td>
</tr>
<tr>
<td width="180" >Row 4</td>
<td width="180" >Cell_H</td>
<td width="180" >Cell_I</td>
<td width="180" >Cell_J</td>
</tr>
<tr>
<td width="180" > Row 5</td>
<td width="540" colspan="4"> Cell_K</td>
</tr>
</table>
I am having trouble using xslt to extract the data from the xml into the table because the rules that I need to apply make it very complicated. Below are the rules that need to apply to the cells that I am having troubles with.
(1) If the <Element1> value is the same throughout the xml, then:
If <Element1> = 'ZZZ' then Cell_B = '10', Cell_C = '20', and Cell_D = '30'
If <Element1> = 'XXX' then Cell_B = '100', Cell_C = '90', and Cell_D = '80'
but if the <Element1> value differs in the xml, then:
Cell_B = '10,100', Cell_C = '20,90', and Cell_D = '30,80'
(2) If the <SubElement5> value is the same throughout the xml, then:
Cell_J = the value of <SubElement5>
but if the <SubElement5> value differs in the xml, then:
Cell_J = the value of both <SubElement5> values separated by a comma
so in this case, the value for Cell_J would be 'Milkshakes, Soda'.
I have been experimenting with different things using:
<xsl:for-each select="./Level1/Element2">
<xsl:value-of select="./SubElement5"/>
</xsl:for-each>
but I cant determine what code I can use to check if they are the same because I can't overwrite the value of a variable.
Edit:
Please note that the cells I've indicated above (cells b,c,d, and j) are the only ones I
need help with. Additionally, for Element1 there are four potential values I can
encounter: ZZZ, XXX, AAA, and BBB. The required values for each of these would be:
(cells b,c, and d)
ZZZ:10,20,30
XXX:100,90,80
AAA:40,30,30
BBB:50,30,20
so if there was only one potential value in the whole xml, the cells should appear with the values as above. If the two element 1 values are different, then the cells should list the above values in each cell, separated by a comma.
With respect to cell_j, I'll try and explain it a little better.
First, I need to determine if <SubElement5> is the same value throughout the xml. In this case it is not, in one section it is Milkshakes and in the other it is 'Soda'. Therefore, cell_J should contain the text 'Milkshakes, Soda'.
If the xml looked like this:
<?xml version="1.0" encoding="UTF-8"?>
<abc1 formName="Form">
<Level1>
<Element1>ZZZ</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Pears</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Milkshakes</SubElement5>
</Element2>
</Level1>
<Level1>
<Element1>XXX</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Kiwifruit</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Milkshakes</SubElement5>
</Element2>
</Level1>
</abc1>
Then the value for cell_j would just be 'Milkshakes'.
Thanks in advance to anyone who can help.
ANSWER TO THIS QUESTION:
To Summarize what Woody did below for anybody's future reference:
<?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="/abc1">
<xsl:variable name="elements" select="//Element1[not(preceding::Element1 = .)]"/>
<table>
<tr>
<td width="180" > Row 1</td>
<td width="540" colspan="4"> Cell_A</td>
</tr>
<tr>
<td width="180" >Types</td>
<td width="180" >Type 1</td>
<td width="180" >Type 2</td>
<td width="180" >Type 3</td>
</tr>
<tr>
<td width="180" >Row 2</td>
<td width="180" > <xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">40</xsl:when>
<xsl:when test=". = 'BBB'">50</xsl:when>
<xsl:when test=". = 'XXX'">100</xsl:when>
<xsl:when test=". = 'ZZZ'">10</xsl:when>
</xsl:choose>
</xsl:for-each></td>
<td width="180" ><xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">30</xsl:when>
<xsl:when test=". = 'XXX'">90</xsl:when>
<xsl:when test=". = 'ZZZ'">20</xsl:when>
</xsl:choose>
</xsl:for-each></td>
<td width="180" ><xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">20</xsl:when>
<xsl:when test=". = 'XXX'">80</xsl:when>
<xsl:when test=". = 'ZZZ'">30</xsl:when>
</xsl:choose>
</xsl:for-each></td>
</tr>
<tr>
<td width="180" > Row 3</td>
<td width="180" >Cell_E</td>
<td width="180" >Cell_F</td>
<td width="180" >Cell_G</td>
</tr>
<tr>
<td width="180" >Row 4</td>
<td width="180" >Cell_H</td>
<td width="180" >Cell_I</td>
<td width="180" ><xsl:for-each select="//SubElement5[not(preceding::SubElement5/text() = text())]">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each></td>
</tr>
<tr>
<td width="180" > Row 5</td>
<td width="540" colspan="4"> Cell_K</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
Woody, thank-you again. This was awesome.
THe key with your problem is that you can't itterate through the data to get what you want, as the answers you want are fixed, ie, if this, output that, if that, output this other. So you need to take each section at a time.
Are there only ever two Level1s?
<xsl:template match="/abc1">
<table>
<tr>
<td width="180" >Row 2</td>
<xsl:choose>
<xsl:when test="not(Level1/Element1='XXX')">
<!-- only ZZZ -->
<td width="180" >10</td>
<td width="180" >20</td>
<td width="180" >30</td>
</xsl:when>
<xsl:when test="not(Level1/Element1='ZZZ')">
<!-- only YYY -->
<td width="180" >100</td>
<td width="180" >90</td>
<td width="180" >80</td>
</xsl:when>
<xsl:otherwise>
<!-- some combination -->
<td width="180" >10,100</td>
<td width="180" >20,90</td>
<td width="180" >30,80</td>
</xsl:otherwise>
</xsl:choose>
</tr>
.. continued
</table>
</xsl:template>
</xsl:stylesheet>
and so on per section. If you have a number of rows, and you want them comma separated then you need to do it per section
or if you have to put one comma list for each section.
Sorry, I couldn't see what you were trying to do with the cell_j and there didn't seem to be a rule for the other ones
Edit: However, if you have a lot of items rather than just 2, and you need a comma separated list, you can do it with xpath, so:
<tr>
<td>
<xsl:for-each select="//SubElement5[not(preceding::SubElement5/text() = text())]">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</td>
.. maybe other rows the same
</tr>
Edit again:
So for your updated question about cell_j, the above comes up with the correct values for it.
For your update in the first section, it would be possible to do a variation on that same theme, if you want all the values shown (so there are 4 values if you have all of your 4 options). Unfortunately as you have fixed values for each, and you have to do each one by one, you would need to do it in a large section, so in a loop:
<xsl:for-each select="//Element1[not(preceding::Element1/text() = text())]">
which would put you in a loop for each unique entry and then have a choose element based on whether that value was XXX, ZZZ etc.
Edit again
There are several ways of doing the first section you want, including recursive functions, external documents and using the node-set function of various different XSLT implimentations, but as a totally generic easy to see way, this is the slightly wordy, but easy to see version (I hope):
<xsl:variable name="elements" select="//Element1[not(preceding::Element1 = .)]"/>
<table>
<tr>
<td width="180" >Row 2</td>
<td>
<xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">40</xsl:when>
<xsl:when test=". = 'BBB'">50</xsl:when>
<xsl:when test=". = 'XXX'">100</xsl:when>
<xsl:when test=". = 'ZZZ'">10</xsl:when>
</xsl:choose>
</xsl:for-each>
</td>
<td>
<xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">30</xsl:when>
<xsl:when test=". = 'XXX'">90</xsl:when>
<xsl:when test=". = 'ZZZ'">20</xsl:when>
</xsl:choose>
</xsl:for-each>
</td>
<td>
<xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">20</xsl:when>
<xsl:when test=". = 'XXX'">80</xsl:when>
<xsl:when test=". = 'ZZZ'">30</xsl:when>
</xsl:choose>
</xsl:for-each>
</td>
</tr>
</table>
actually - I am not sure if the iterating on a variable ($elements in this case) is that standard, it may be a saxon and msxsl thing, but if it isn't, you can replace it with the value (//Element1[not(preceding::Element1 = .)])

XSL sum() and variable/loop

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.

XML to table, how to get position() of context node to the specified ancestor(s)

I have such xml:
<A1>
<B1>
<C1>
<C2>
<C3>
</B1>
<B2>
<C4>
<C5>
<C6>
</B2>
</A1>
<A2>
<B3>
<C7>
<C8>
<C9>
</B3>
<B4>
<C10>
<C11>
<C12>
</B4>
</A2>
I need to transform it to table with nested rows:
<table border="yes">
<tr>
<td>A1</td>
<td>B1</td>
<td>C1</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C2</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C3</td>
</tr>
<tr>
<td></td>
<td>B2</td>
<td>C3</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C4</td>
</tr>
A and B appear only if they are new (not repeating in every row);
I'm trying to use position()
<xsl:template match="c">
<tr>
<td>
<xsl:if test="IT IS THE FIRST C IN A">
<xsl:value-of select="ancestor::A"/>
</xsl:if>
</td>
<td>
<xsl:if test="position(1)">
<xsl:value-of select="parent"/>
</xsl:if>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
It seems that we should emulate position() for ancestor.
Is there a general solution for any number of nested rows?
You perhaps need something like the following (if I understood your question correctly):
<xsl:template match="C">
<tr>
<td>
<xsl:if test="generate-id(ancestor::A/descendant::C[1]) = generate-id(.)">
<xsl:value-of select="ancestor::A"/>
</xsl:if>
</td>
<td>
<xsl:if test="not(previous-sibling::C)">
<xsl:value-of select=".."/>
</xsl:if>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="descendant::C"/>
</xsl:template>
Edit: you may also use
not(previous-sibling::C) and not(../previous-sibling::B)
as the first test (instead of using generate-id()).