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.
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 am trying to implement a logic which would do choosing a value if it matches the condition. The problem is, if my list contains more elements than one, it wouldn't display the value which matches the condition, but only the first one. Could you provide some advice? thanks. I need this to implement multiple filtering by conditions. Meaning that in this for-each I would display only books with year 2008, but in another table I could also use 2010 from the list of years. The below example is a part of the template for one table which uses 2008, but I will have multiple tables where I would filter books by 2010 as well.
XSL:
<xsl:for-each select="years">
<xsl:choose>
<xsl:when test="year=2008">
<td class="year"><xsl:value-of select="year"/></td>
</xsl:when>
</xsl:choose>
</xsl:for-each>
XML:
<book>
<title>Professional ASP.NET 4 in C# and VB</title>
<author>Bill Evjen, Scott Hanselman, Devin Rader</author>
<country>USA</country>
<price>580</price>
<years>
<year>2010</year>
<year>2008</year>
</years>
</book>
This one would populate 2010 into my HTML, not 2008 as I would expect. Is this doable at all?
Update:
I've tried this approach for example to filter against multiple conditions separately:
<xsl:for-each select="library/book">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<xsl:for-each select="years/year[.2008]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
<xsl:for-each select="years/year[.2010]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
This would not populate any td at all in my case..
If you only want to display books for a specific year, you would put a condition in the xsl:for-each that selects the book
<xsl:for-each select="library/book[years/year=$year]">
Where $year is a variable (or parameter) containing the year you want
Do note, if you know you are dealing with a specific year, then you don't actually need to do <xsl:value-of select="year" />, you can just output the year value you are working with.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<xsl:call-template name="table">
<xsl:with-param name="year" select="2008" />
</xsl:call-template>
</xsl:template>
<xsl:template name="table">
<xsl:param name="year" />
<table>
<xsl:for-each select="library/book[years/year=$year]">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<td class="year">
<xsl:value-of select="$year"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Note, you could also use a key here to look up books by year
<xsl:key name="booksByYear" match="book" use="years/year" />
Then, the xsl:for-each to select books, looks like this:
<xsl:for-each select="key('booksByYear', $year)">
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>
I have an XML file that I export from my DB which is structured as below,
'
<output>
<row>
<Month>October</Month>
<Location>kansas</Location>
<bus_name>bus1</bus_name>
<bus_type>volvo</bus_type>
<bus_colour>red</bus_colour>
<bus_count>10</bus_count>
</row>
<row>
<Month>October</Month>
<Location>kansas</Location>
<bus_name>bus1</bus_name>
<bus_type>Volvo</bus_type>
<bus_colour>green</bus_colour>
<bus_count>11</bus_count>
</row>
<Month>October</Month>
<Location>kansas</Location>
<bus_name>bus1</bus_name>
<bus_type>Merc</bus_type>
<bus_colour>blue</bus_colour>
<bus_count>5</bus_count>
</row>
So on...
</output>
I need the table to look like the image attached below. The XSL and XML file will be refreshed periodically.The cells will have similar color's based on bus type.
I'm new to XSL thus having a really hard time coming up with a solution. Any help would be appreciated.
Just as a different approach and also handling the colours for the same bus_types.
Demo
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<table>
<tr>
<td colspan="9">ACME BUS SERVICE</td>
</tr>
<tr>
<td colspan="9">
Month: <xsl:value-of select="//Month"/>
</td>
</tr>
<tr>
<td>Season</td>
<td>Location</td>
<td>Bus Name</td>
<td colspan="2">RED</td>
<td colspan="2">GREEN</td>
<td colspan="2">BLUE</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>Bus Type</td>
<td>Bus Count</td>
<td>Bus Type</td>
<td>Bus Count</td>
<td>Bus Type</td>
<td>Bus Count</td>
</tr>
<xsl:for-each select="//row[Location[not(preceding::Location/. = .)]]" >
<xsl:variable name="currentLocation" select="./Location"/>
<tr>
<xsl:attribute name="class">
<xsl:value-of select="$currentLocation"/>
</xsl:attribute>
<td>
<xsl:if test="position()=1">Winter</xsl:if>
</td>
<td>
<xsl:value-of select="$currentLocation"/>
</td>
<td>
<xsl:value-of select="./bus_name"/>
</td>
<td>
<xsl:if test="count(//row[Location= $currentLocation]
[bus_type = //row[Location= $currentLocation]
[bus_colour = 'red']/bus_type]) > 1">
<xsl:attribute name="class">color</xsl:attribute>
</xsl:if>
<xsl:value-of select="//row[Location= $currentLocation]
[bus_colour = 'red']/bus_type"/>
</td>
<td>
<xsl:value-of select="//row[Location= $currentLocation]
[bus_colour = 'red']/bus_count"/>
</td>
<td>
<xsl:if test="count(//row[Location= $currentLocation]
[bus_type = //row[Location=$currentLocation]
[bus_colour = 'green']/bus_type]) > 1">
<xsl:attribute name="class">color</xsl:attribute>
</xsl:if>
<xsl:value-of select="//row[Location= $currentLocation]
[bus_colour = 'green']/bus_type"/>
</td>
<td>
<xsl:value-of select="//row[Location= $currentLocation]
[bus_colour = 'green']/bus_count"/>
</td>
<td>
<xsl:if test="count(//row[Location= $currentLocation]
[bus_type = //row[Location=$currentLocation]
[bus_colour = 'blue']/bus_type]) > 1">
<xsl:attribute name="class">color</xsl:attribute>
</xsl:if>
<xsl:value-of select="//row[Location= $currentLocation]
[bus_colour = 'blue']/bus_type"/>
</td>
<td>
<xsl:value-of select="//row[Location= $currentLocation]
[bus_colour = 'blue']/bus_count"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
For every location, the location is set as classname to the <tr>, e.g. <tr class="kansas">. Every td with a bus type that is used more than once at a location gets the class="color". So to display the table with different colors, you can just add CSS like e.g. .kansas .color { background-color: blue; }. In case you want to display the same color based on the bus_type, just adjust the classname "color" in the xslt to the current bus_type.
Note: In the linked example I've only added one row for Texas to show that the XSLT displays multiple locations, only sets the season for the first one, and will also work in case not all colors are provided for a location. And the output is not valid HTML (no html-, head-, body-tags etc provided). As you mentioned you'd like to get HTML ouput, you probably already have an XSLT generating valid HTML where you can adjust/include the part you need for the table.
Update for the question in the comments: To set the class name for a <tr> to the name of the bus_type (in case a bus_type is used more than once at a location) instead of the location:
Change this in above XSLT:
<tr>
<xsl:attribute name="class">
<xsl:value-of select="$currentLocation"/>
</xsl:attribute>
into
<tr>
<xsl:if test="count(//row[Location=$currentLocation]) >
count(//row[Location=$currentLocation]/
bus_type[not(. = preceding::bus_type)])">
<xsl:attribute name="class">
<xsl:value-of select="//row[Location=$currentLocation]/
bus_type[ . = preceding::bus_type]"/>
</xsl:attribute>
</xsl:if>
Updated Demo 2 for this.
Additional notes and questions: One adjustment to the OP XML was to change the lowercase "volvo" to "Volvo". In case the original export from DB really mixes upper- and lowercase names, this can be handled in the XSLT to lowercase all bus_names (to get the unique values) and uppercase the first letter for the value in the <td>. Also it would be good to know if you use XSLT 2.0 or XSLT 1.0 as XSLT 2.0 provides functionality to simplify some tasks - e.g. 2.0 provides a lower-case() function where in 1.0 the same can be achieved using translate() - as reference for this as you mentioned you're new to XSL: How can I convert a string to upper- or lower-case with XSLT?
Further question is - as the XML example is only a part of the DB export - if there will be only one row for each location or if it is possible that there are various rows, e.g. kansas bus1, kansas bus2 etc.
Update 2 for the second question in the comments: I can add an (almost) line by line explanation and will drop a comment when done. I assume it's not necessary to cover the HTML part but only the XSLT. In the meantime, as you mentioned you're new to XSLT, maybe the following can be of use:
for <xsl:template match="/"> - https://stackoverflow.com/questions/3127108/xsl-xsltemplate-match
for XPath axes - http://www.xmlplease.com/axis
for some basics, e.g. - In what order do templates in an XSLT document execute, and do they match on the source XML or the buffered output?
Note that it should be avoided at SO to have extended comments - when there are too many comments below a post, an automated message will be displayed that suggests to move to chat. Because you need a reputation of 20 to chat (https://stackoverflow.com/help/privileges), this won't be possible at the moment.
The first three node has to be mapped with the first row
Now that is a specific question. Assuming your input is arranged so that each group of 3 consecutive <row> elements maps to a single table row (with internal group positions matching the column positions), try it this way:
<?xml version="1.0" encoding="UTF-8"?>
<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="/output">
<table border="1">
<tr>
<!-- build your header here -->
</tr>
<xsl:for-each select="row[position() mod 3 = 1]" >
<tr>
<td><xsl:value-of select="Location"/></td>
<td><xsl:value-of select="bus_name"/></td>
<xsl:for-each select=". | following-sibling::row[position() < 3]">
<td><xsl:value-of select="bus_type"/></td>
<td><xsl:value-of select="bus_colour"/></td>
<td><xsl:value-of select="bus_count"/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
I suggest you ask a separate question regarding the coloring. Make sure we understand exactly what is known beforehand (e.g. a list of known bus types?) and what is the required output (post it as code).
I have a stylesheet I'm using with a perl module that only works with XSLT 1.0. I want to create a JSON array inside a JSON dictionary so I need proper comma seperation for the elements. I'm parsing an XHTML table where there are 1 or more spans in the second cell. So for-each select="./tr" and then for-each select="./td[1]/span" or something like that.
After changing it a little it behaves as expected as Ian said it would.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" doctype-public="-//W3C//DTD HTML 4.01//EN" encoding="UTF-8" />
<xsl:template match="text()">
</xsl:template>
<xsl:template match="table/tbody">
<xsl:text>[</xsl:text>
<xsl:for-each select="./tr[not(#class='no-results')]">
<xsl:text>{"</xsl:text>
<xsl:value-of select="normalize-space(.//strong)" />
<xsl:text>":{"ingredients":{</xsl:text>
<xsl:for-each select=".//div[#class='reagent-list']//a[#class='item-link reagent']">
<xsl:value-of select="substring(./#href, 14)" />:<xsl:value-of select="normalize-space(./span[1])" />
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>}</xsl:text>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:template>
</xsl:stylesheet>
I realize that the stylesheet does not match the xml below. The actual document is huge. I hope you understand what I mean, though. I just made this up:
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td><span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
</tbody>
</table>
=>
{
"Row one":["an element"],
"Row two":["an element", "an element"],
"Row three":["an element", "an element", "an element", "an element"]
}
Instead I get this:
{
"Row one":["an element",],
"Row two":["an element", "an element",],
"Row three":["an element" "an element" "an element" "an element"]
}
I've been using position() and last() in a test tag to print a comma and it seems to work correctly for the outer loop, but how do I tell my test tag to use the inner for-each scope when printing the commas that seperate the array?
As mentioned by #IanRoberts, it is difficult to give targeted assistance without seeing what your existing XSLT looks like.
That said, here is a solution that is push-oriented (i.e., no <xsl:for-each>) and does not require last().
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my"
exclude-result-prefixes="my"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" method="text" />
<xsl:strip-space elements="*" />
<my:ones>
<num>one</num>
<num>two</num>
<num>three</num>
<num>four</num>
<num>five</num>
<num>six</num>
<num>seven</num>
<num>eight</num>
<num>nine</num>
</my:ones>
<xsl:template match="/*">
<xsl:text>{
</xsl:text>
<xsl:apply-templates select="tbody/tr" />
<xsl:text>
}</xsl:text>
</xsl:template>
<xsl:template match="tr">
<xsl:variable name="vPos" select="position()" />
<xsl:if test="$vPos > 1">,
</xsl:if>
<xsl:text> "Row </xsl:text>
<xsl:value-of select="document('')/*/my:ones/*[$vPos]" />
<xsl:text>":[</xsl:text>
<xsl:apply-templates select="td[2]/span" />
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template match="span">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="concat('"', ., '"')" />
</xsl:template>
</xsl:stylesheet>
...is run against the provided XML:
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td>
<span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td>
<span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td>
<span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
</tbody>
</table>
...the wanted result is produced:
{
"Row one":["an element"],
"Row two":["an element", "an element"],
"Row three":["an element", "an element", "an element", "an element"]
}
Explanation:
The first template matches the root element. It's purpose is to apply templates to that element's <tr> grandchildren and sandwich those results between { and } (adding newlines as appropriate).
The second template matches <tr> elements. It outputs row information and is instructed to apply templates to all <span> children of the second <td> element (again, sandwiching the results between braces and other text as necessary).
NOTE: instead of using last(), you'll see that the first element outputs a comma, followed by a newline, if the position of this <tr> in the current context is greater than 1. This has the same effect of applying commas correctly; it's merely a different way of looking at the same problem (and is what I use because it seems more efficient to me ;) ).
NOTE: to make this solution more extensible, you'll see that I'm not statically outputting the words "one", "two", etc. in each row. Instead, at the top of the XSLT, I've defined a <my:ones> element to hold onto the text values of each "ones" number. When processing each <tr>, I use the position of that <tr> in the current context to retrieve the correct <num> element's value. I've left it as an exercise to the reader, but it would indeed be possible to define <my:tens>, <my:hundreds>, etc. to scale this solution up to potentially large numbers of rows.
The final template matches <span> elements. Again, it uses an <xsl:if> element to test whether the position of this <span> in the current context is greater than 1; if so, a comma (followed by a space) is output. After that, we merely concatenate two " symbols with the value of the span sandwiched in-between.