Here is some XSL script:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msa="http://www.publictalksoftware.co.uk/msa">
<xsl:output method="html" indent="yes" version="4.01"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
doctype-public="//W3C//DTD XHTML 1.0 Transitional//EN"/>
<xsl:variable name="DutyHistory" select="document('DutyAssignHistory.XML')"/>
<xsl:template match="/">
<html>
<head>
<title>Test</title>
</head>
<body>
<xsl:for-each select="MeetingWorkBook/Meeting">
<p>
<xsl:value-of select ="Date/#ThisWeek"/>
</p>
<xsl:variable name="Week" select="Date/#ThisWeek"/>
<table>
<tr>
<td>Sound</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='1' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Platform</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='5' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Left Mike</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='7' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Right Mike</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='8' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Public Talk Chairman</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='4' and #IndexType='Custom']"/>
</td>
</tr>
<tr>
<td>Watchtower Reader</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='5' and #IndexType='Custom']"/>
</td>
</tr>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
As you can see it is linking in another XML document for reference. Here is one example of that linked in file:
<?xml version="1.0" encoding="utf-8"?>
<DutyAssignmentHistory xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.publictalksoftware.co.uk/msa">
<DutyAssignments>
<DutyAssignmentEntry Date="2018-01-04" Week="W20180101" Template="0" Mode="Midweek">
<Assignment Index="2" IndexType="Fixed">Name 1</Assignment>
<Assignment Index="5" IndexType="Fixed">Name 2</Assignment>
<Assignment Index="7" IndexType="Fixed">Name 3</Assignment>
<Assignment Index="8" IndexType="Fixed">Name 4</Assignment>
<Assignment Index="13" IndexType="Fixed">Name 5</Assignment>
<Assignment Index="14" IndexType="Fixed">Name 6</Assignment>
</DutyAssignmentEntry>
</DutyAssignments>
</DutyAssignmentHistory>
Potentially a user might want to reference the information in the XML and display it however they like but I am wanting to show them the simplest method.
As you can see there are several criteria:
Week (WYYYYMMDD)
Mode (Midweek, Weekend or Weekly)
Template(0 or higher)
The above will filter to the right week of assignments. Then, to identify the actual assignment:
Index (numeric value)
IndexType (Fixed, CustomFixed or Custom)
Can I use templates in any way (perhaps with variables) to simplify the code as it is getting repetative?
You could use a template and pass parameters, or in XSLT 2.0 or higher you could also define a function, which makes it much easier to use and saves some typing. But for what you are currently doing, a variable and some predicate filters seems to be the most simple and easy.
The most simple and easy way would be to bind a variable with the weekend assignments, and then apply your predicate filter to select the one with the #Index and #IndexType:
<xsl:variable name="Week" select="Date/#ThisWeek"/>
<xsl:variable name="weekend-assignments"
select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments
/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment"/>
<table>
<tr>
<td>Sound</td>
<td>
<xsl:value-of select="$weekend-assignments[#Index='1' and #IndexType='Fixed']"/>
</td>
</tr>
If you make the variable hold an unfiltered set of Assignment elements, you could perform all of the filtering in the predicates:
<xsl:variable name="Week" select="Date/#ThisWeek"/>
<xsl:variable name="assignments"
select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments
/msa:DutyAssignmentEntry/msa:Assignment"/>
<table>
<tr>
<td>Sound</td>
<td>
<xsl:value-of
select="$assignments[#Index='1' and #IndexType='Fixed']
[..[#Week=$Week and #Mode='Weekend' and #Template='0']]"/>
</td>
</tr>
If you want to consolidate the logic for generating the columns, you could define a template for msa:Assignment:
<xsl:template match="msa:Assignment">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
And then use it like this:
<table>
<tr>
<td>Sound</td>
<xsl:apply-templates select="$weekend-assignments[#Index='1' and #IndexType='Fixed']"/>
If you want to consolidate the logic for generating rows, you could define a template for msa:Assignment and send in a parameter for the first column:
<xsl:template match="msa:Assignment">
<xsl:param name="label"/>
<tr>
<td><xsl:value-of select="$label"/></td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
And then use it like this:
<table>
<xsl:apply-templates select="$weekend-assignments[#Index='1' and #IndexType='Fixed']">
<xsl:with-param name="label" select="'Sound'"/>
</xsl:apply-templates>
Related
I've just started learning XML/XSL and I've hit a roadblock in one of my assignments. Tried Googling and searching over here but I can't seem to find a question that has a solution that is basic. so what I am trying is to display rows of bucket-type and room-types associated with it. can somebody please help
<list-inventory list-count="2">
<list list-type="Standard" list-order = "1" count-Types = "3">
<types type="BEN2D"></room>
<types type="BESH2D"></room>
<types type="HNK"></room>
</list>
<list list-type="Deluxe" list-order = "2" count-Types = "3">
<types type="SNK"></room>
<types type="TESTKD"></room>
<types type="TESTKD"></room>
</list>
<list-inventory>
I want table as below
Standard | Deluxe
BEN2D |SNK
BESH2D |TESTKD
HNK |TESTKD
I tried below xsl code but i see all list-type in single column and only 1st is being printing for all list-type:
<xsl:for-each select="/contents/list-inventory/list">
<tr>
<td class="alt-th" style="border:1px solid black">
<xsl:value-of select="#list-type"/>
</td>
</tr>
<tr>
<td style="border:1px solid black">
<xsl:for-each select="/contents/list-inventory/list/types">
<span><xsl:value-of select="#type"/></span>
<xsl:if test="position()!=last()">
<br/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
Can someone help me with xsl:for-each inside a xsl:for-each
It's too bad you did not post your expected result as code. I would assume that you want a separate row for each pair of values. As I stated in the comments, this is far from being trivial.
However, you could make it simpler if you are willing to settle for a single data row, where each cell contains all the values of the corresponding list (your attempt seems to suggest that this is what you actually tried to accomplish).
So, given a well-formed XML input:
XML
<list-inventory list-count="2">
<list list-type="Standard" list-order = "1" count-Types = "3">
<types type="BEN2D"/>
<types type="BESH2D"/>
<types type="HNK"/>
</list>
<list list-type="Deluxe" list-order = "2" count-Types = "3">
<types type="SNK"/>
<types type="TESTKD"/>
<types type="TESTKD"/>
</list>
</list-inventory>
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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/list-inventory">
<table border="1">
<!-- header -->
<tr>
<xsl:for-each select="list">
<th>
<xsl:value-of select="#list-type"/>
</th>
</xsl:for-each>
</tr>
<!-- body -->
<tr>
<xsl:for-each select="list">
<td>
<xsl:for-each select="types">
<xsl:value-of select="#type"/>
<br/>
</xsl:for-each>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
to get:
Result
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<th>Standard</th>
<th>Deluxe</th>
</tr>
<tr>
<td>BEN2D<br/>BESH2D<br/>HNK<br/>
</td>
<td>SNK<br/>TESTKD<br/>TESTKD<br/>
</td>
</tr>
</table>
which would render as:
I'm not sure how general you want your solution to be (i.e what inputs does it have to handle other than the example shown), but I would do something like:
<xsl:template match="list-inventory">
<xsl:variable name="list2" select="list[2]/types"/>
<xsl:for-each select="list[1]/types">
<xsl:variable name="position" select="position()"/>
<tr>
<td><xsl:value-of select="#type"/></td>
<td><xsl:value-of select="$list2[$position]/#type"/></td>
</tr>
</xsl:for-each>
</xsl:template>
Both the variables here are needed to avoid problems with context: the effect of an XPath expression depends on the context at the time it is evaluated, so you can evaluate a variable to capture information at the time you're in the right context.
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>
Ok, I'm working through some simple tutorials from here:
http://www.cch.kcl.ac.uk/legacy/teaching/7aavdh06/xslt/html/module_06.html
The first exercise involves creating a transformation that produces a certain output. Unfortunately, although I'm close, I get an unwanted element at the start. i.e.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xhtml"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" />
<xsl:template match="/div/placeName">
<html>
<head />
<body>
<Table>
<tr>
<td>Place Name</td>
<td>
<xsl:value-of select="name" />
</td>
</tr>
<tr>
<td>Place Name (regularised)</td>
<td>
<xsl:value-of select="#reg" />
</td>
</tr>
<tr>
<td>National Grid Reference</td>
<td>
<xsl:value-of select="#key" />
</td>
</tr>
<tr>
<td>Type of building/monument</td>
<td>
<xsl:value-of select="settlement/#type" />
</td>
</tr>
</Table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
but the output I'm getting is:
Location
Place Name Old Warden
Place Name (regularised) Old Warden, St Leonard
National Grid Reference TL 137 443
Type of building/monument Parish church
The rest is fine but the 'Location' is unwanted. The source XML is at the link above. Any idea how I stop the unwanted text appearing? Or, better still, tell me where I'm going wrong! :)
Edit: Here is the output
<?xml version="1.0" encoding="utf-8" ?>
Location
<!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body>
<table>
<tr>
<td>Place Name</td>
<td>Old Warden</td>
</tr>
<tr>
<td>Place Name (regularised)</td>
<td>Old Warden, St Leonard</td>
</tr>
<tr>
<td>National Grid Reference</td>
<td>TL 137 443</td>
</tr>
<tr>
<td>Type of building/monument</td>
<td>Parish church</td>
</tr>
</table>
</body>
</html>
As Stivel mentions, the "Location" text does come from the head element in your XML.
<div type="location">
<head n="I">Location</head>
<placeName reg="Old Warden, St Leonard" key="TL 137 443">
The reason it is appearing is because of XSTL's built-in templates which it uses when you do not specify a match for an element it is looking for in your XSLT.
You can read up on built-in templates at the W3C page but in short, if XSLT can't find a match it will either continue processing the element's children (without copying the element), or in the case of text or attributes, output the value.
XSLT will start by looking for a match for the document element first, and if you have not provided a template, it will continue looking for a template for the root element, and then its children, and so on.
In your case, you have not provided a template to match anything until /div/placeName, this means XSLT will use the built-in template for the div element. This has two children; head and placeName. You have a template it can use for placeName, but not head and so the built-in template ends up outputing the text for head because you have not told it anything otherwise.
The solution is to simply to add a template to ignore the head element
<xsl:template match="/div/head" />
Here is the full XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xhtml"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" indent="yes" />
<xsl:template match="/div/head" />
<xsl:template match="/div/placeName">
<html>
<head />
<body>
<Table>
<tr>
<td>Place Name</td>
<td>
<xsl:value-of select="name" />
</td>
</tr>
<tr>
<td>Place Name (regularised)</td>
<td>
<xsl:value-of select="#reg" />
</td>
</tr>
<tr>
<td>National Grid Reference</td>
<td>
<xsl:value-of select="#key" />
</td>
</tr>
<tr>
<td>Type of building/monument</td>
<td>
<xsl:value-of select="settlement/#type" />
</td>
</tr>
</Table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
When you use this, this should give the output you need.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xhtml" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<xsl:template match="div">
<xsl:apply-templates select="placeName"/>
</xsl:template>
<xsl:template match="placeName">
<html>
<head />
<body>
<Table>
<tr>
<td>Place Name</td>
<td>
<xsl:value-of select="name" />
</td>
</tr>
<tr>
<td>Place Name (regularised)</td>
<td>
<xsl:value-of select="#reg" />
</td>
</tr>
<tr>
<td>National Grid Reference</td>
<td>
<xsl:value-of select="#key" />
</td>
</tr>
<tr>
<td>Type of building/monument</td>
<td>
<xsl:value-of select="settlement/#type" />
</td>
</tr>
</Table>
</body>
</html>
</xsl:template>
Probably your <head/> may refer
<head n="I">Location</head>
remove <head/> in xsl and check that.
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.
I need to turn
<question>
<static><![CDATA[Static Data]]></static>
<debit-row />
<debit-row />
<credit-row />
<header><![CDATA[Header HTML 1]]></header>
<debit-row />
<debit-row />
<credit-row />
</question>
into
<p>Static Data</p>
<ul>
<li>
<table>
<tr><td> debit row </td></tr>
<tr><td> credit row </td></tr>
<tr><td> credit row </td></tr>
</table>
</li>
<li> Header HTML 1
<table>
<tr><td> debit row </td></tr>
<tr><td> debit row </td></tr>
<tr><td> credit row </td></tr>
</table>
</li>
</ul>
Essentially, either a header or a debit-row indicates the start of a new chunk. Each chunk is a list item. Each set or rows is a table (as a rule, credit rows always come last so it's easy to tell when to start the table).
XSLT and XPATH seem very difficult and I'm having a very hard time looking up anything that I want to do at all, so if anyone has an excellent reference, I would appreciate that too.
I've started out with this xsl:
<xsl:template match="question">
<xsl:apply-templates select="static|header|debit-row[preceding-sibling::*[1] != header]" />
</xsl:template>
This is not a good start, because the templates are not applied to any debit-row at all, but they should be applied to the very first debit-row (it does not have a header element preceding it). Is that expression wrong?
Even if I get that to work, I need to find a way to say "Open a <ul> if this is the very first header or debit-row," and I'm not sure how to do that when applying the header/debit-row template. debit-row each has its own xml to be applied too (it needs a table row and td). I also have to open and close the table appropriately before the first debit-row and after the last credit-row.
I would seriously appreciate any help as I am stuck even getting the simple xpath expression above to work correctly.
I. XSLT 1.0 solution:
<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:key name="kFollowing"
match="*[not(self::static or self::header)]"
use="generate-id(preceding-sibling::*
[self::static
or
self::header
][1]
)"/>
<xsl:template match="/*[static]">
<p><xsl:value-of select="static"/></p>
<ul>
<xsl:apply-templates select="static|header"/>
</ul>
</xsl:template>
<xsl:template match="static|header">
<li>
<xsl:value-of select=
"concat(self::header, '
')"/>
<table>
<xsl:apply-templates
select="key('kFollowing', generate-id())"/>
</table>
</li>
</xsl:template>
<xsl:template match=
"*/*[not(self::static or self::header)]">
<tr>
<td>
<xsl:value-of select=
"translate(name(),'-', ' ')"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<question>
<static><![CDATA[Static Data]]></static>
<debit-row />
<debit-row />
<credit-row />
<header><![CDATA[Header HTML 1]]></header>
<debit-row />
<debit-row />
<credit-row />
</question>
produces the wanted, correct result:
<p>Static Data</p>
<ul>
<li>
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
<li>Header HTML 1
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
</ul>
Explanation: Positional grouping using a key to define all elements that belong to a group.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*[static]">
<p><xsl:value-of select="static"/></p>
<ul>
<xsl:for-each-group select="*"
group-starting-with="static|header">
<li>
<xsl:value-of separator="
" select=
"current-group()[1][self::header], ''"/>
<table>
<xsl:apply-templates
select="current-group()[position() gt 1]"/>
</table>
</li>
</xsl:for-each-group>
</ul>
</xsl:template>
<xsl:template match=
"*/*[not(self::static or self::header)]">
<tr>
<td>
<xsl:value-of select=
"translate(name(),'-', ' ')"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
when this XSLT 2.0 transformation is applied to the same XML document (above), again the same, correct result is produced:
<p>Static Data</p>
<ul>
<li>
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
<li>Header HTML 1
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
</ul>
Explanation: Using the XSLT 2.0 <xsl:for-each-group> instruction with a group-starting-with attribute. Also using the standard XSLT 2.0 function current-group().