XSLT - format comma seperated list into table format - xslt

A web service is returning a list of persons in the below xml format. The values are seperated by a semicolon. I need to display the values in 2 or 3 columns (Needs to be a variable).
Desired Results:
<table border="1">
<tr>
<td>Smith, John</td>
<td>Jackson, Samuel</td>
<td>Wayne, Bruce</td>
</tr>
<tr>
<td>Cosby, Bill</td>
<td>Kent, Clarke</td>
<td>Leno, Jay</td>
</tr>
<tr>
<td>OBrian, Conan</td>
<td> </td>
<td> </td>
</tr>
</table>
Xml sample
<?xml version="1.0" encoding="UTF-8"?> <PI>Smith, John; Jackson,
Samuel; Wayne, Bruce; Cosby, Bill; Kent, Clarke; Leno, Jay; OBrian,
Conan; </PI>

Please accept Holman's solution. His technique is spot-on.
XSLT 2.0 Solution
Your sample input data suggests semicolon terminated list, rather than a semicolon separated list.
This XSLT 2.0 stylesheet will work correctly with either a semicolon terminated list or a semicolon separated list. It takes one input parameter col-count, which defaults to 3.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:param name="col-count" select="3" />
<xsl:template match="/*">
<table border="1">
<xsl:variable name="names" select="tokenize(.,';')[normalize-space(.)]" />
<xsl:for-each-group select="$names , for $x in 1 to
($col-count - (count($names) mod $col-count)) mod $col-count return ''"
group-by="(position() - 1) idiv $col-count">
<tr>
<xsl:for-each select="current-group()">
<td><xsl:value-of select="normalize-space(.)" /></td>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
Notes:
(1) Notice the brevity of the solution, and the absence of clumsy xsl:if statements. The use of calculated sequences (as opposed to input document node selections), is not just limited to the group-by attribute, but can also to applied to select. The use of computed sequences suggests a more functional view, rather than a procedural view.
(2) If you want the output to be safe for old browsers, then replace ...
<xsl:for-each-group select="$names , for $x in 1 to
($col-count - (count($names) mod $col-count)) mod $col-count return ''"
group-by="(position() - 1) idiv $col-count">
... with ...
<xsl:for-each-group select="$names , for $x in 1 to
($col-count - (count($names) mod $col-count)) mod $col-count return ' '"
group-by="(position() - 1) idiv $col-count">
XSLT 1.0 Solution
If you are stuck on XSLT 1.0, then you could use this equivalent, but less efficient stylesheet ...
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:esl="http://exslt.org/common"
xmlns:so="http://stackoverflow.com/questions/18066463"
version="1.0"
exclude-result-prefixes="xsl so esl">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="col-count" select="3" /><!-- Must be 2 or 3. -->
<xsl:variable name="empty-rtf">
<so:name> </so:name>
</xsl:variable>
<xsl:variable name="empty" select="esl:node-set($empty-rtf)/*" />
<xsl:template match="/*">
<table border="1">
<xsl:variable name="names-rtf">
<xsl:call-template name="split">
<xsl:with-param name="list" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="names" select="esl:node-set($names-rtf)/*" />
<xsl:for-each select="$names[(position() mod $col-count) = 1]">
<xsl:variable name="row" select="position() - 1" />
<tr>
<xsl:apply-templates select="$names[floor((position() - 1) div $col-count) = $row]" />
<xsl:if test="position()=last()">
<xsl:for-each select="($names|$empty)[position() <=
(($col-count - (count($names) mod $col-count)) mod $col-count)]">
<xsl:apply-templates select="$empty" />
</xsl:for-each>
</xsl:if>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="split">
<xsl:param name="list" />
<xsl:choose>
<xsl:when test="contains($list,';')">
<xsl:call-template name="split">
<xsl:with-param name="list" select="substring-before($list,';')" />
</xsl:call-template>
<xsl:call-template name="split">
<xsl:with-param name="list" select="substring-after($list,';')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="normalize-space($list)">
<so:name><xsl:value-of select="normalize-space($list)" /></so:name>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
<xsl:template match="so:*" priority="2">
<td><xsl:value-of select="." /></td>
</xsl:template>
</xsl:stylesheet>

Here is an XSLT 2.0 solution with the number of columns as a variable as requested. This employs a technique I teach in the classroom that underscores that grouping need not be by values in the XML (as many XSLT users assume), rather, it can be by any arbitrary calculation (in this case, the result of division).
[edited to show multiple invocations with different number of columns]
t:\ftemp>type names.xml
<?xml version="1.0" encoding="UTF-8"?> <PI>Smith, John; Jackson,
Samuel; Wayne, Bruce; Cosby, Bill; Kent, Clarke; Leno, Jay; OBrian,
Conan; </PI>
t:\ftemp>call xslt2 names.xml names.xsl names.out.xml "cols=3"
t:\ftemp>type names.out.xml
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<td>Smith, John</td>
<td>Jackson, Samuel</td>
<td>Wayne, Bruce</td>
</tr>
<tr>
<td>Cosby, Bill</td>
<td>Kent, Clarke</td>
<td>Leno, Jay</td>
</tr>
<tr>
<td>OBrian, Conan</td>
<td/>
<td/>
</tr>
</table>
t:\ftemp>call xslt2 names.xml names.xsl names.out.xml "cols=2"
t:\ftemp>type names.out.xml
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<td>Smith, John</td>
<td>Jackson, Samuel</td>
</tr>
<tr>
<td>Wayne, Bruce</td>
<td>Cosby, Bill</td>
</tr>
<tr>
<td>Kent, Clarke</td>
<td>Leno, Jay</td>
</tr>
<tr>
<td>OBrian, Conan</td>
<td/>
</tr>
</table>
t:\ftemp>type names.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:param name="cols" required="yes"/>
<xsl:template match="PI">
<table border="1">
<!--determine population and group by number of columns-->
<xsl:for-each-group select="tokenize(.,';\s+')"
group-by="(position()-1) idiv $cols">
<tr>
<!--put members into the row-->
<xsl:for-each select="current-group()">
<td>
<xsl:value-of select="normalize-space(.)"/>
</td>
</xsl:for-each>
<!--filler for the last row-->
<xsl:if test="position()=last()">
<xsl:for-each select="count(current-group())+1 to $cols">
<td/>
</xsl:for-each>
</xsl:if>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem

Related

Dynamic/Generic XSLT for Verical and Horizontal Layouts

I have the following XSLT. It works great for horizontal layouts. However when there are too many columns I need it to flip to a vertical layout. Ultimately being able to configure or specify the number of columns in a variable would be best. Can anyone kindly help?
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/*">
<table>
<thead>
<tr>
<xsl:apply-templates select="*[1]/*" mode="th"/>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="*"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/*" mode="th">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="/*/*">
<tr>
<xsl:apply-templates select="*"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
I am not sure I have understood the question correctly but if you just want to output several tables, each having $cols-per-table columns, then I think the adaption of the presented code to
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="cols-per-table" select="10"/>
<xsl:output method="html" indent="yes" doctype-system="about:legacy-doctype"/>
<xsl:template match="/">
<xsl:apply-templates select="*/*[1]/*[position() mod $cols-per-table = 1]" mode="page"/>
</xsl:template>
<xsl:template match="*" mode="page">
<xsl:param name="page-no" select="position()"/>
<table border="1">
<thead>
<xsl:apply-templates select=". | following-sibling::*[position() < $cols-per-table]" mode="th"/>
</thead>
<tbody>
<xsl:apply-templates select="/*/*">
<xsl:with-param name="page-no" select="$page-no"/>
</xsl:apply-templates>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/*" mode="th">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="/*/*">
<xsl:param name="page-no"/>
<xsl:variable name="first-col" select="1 + $cols-per-table * ($page-no - 1)"/>
<xsl:variable name="last-col" select="$first-col + $cols-per-table - 1"/>
<tr>
<xsl:apply-templates select="*[position() >= $first-col and position() <= $last-col]"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYjr/1
I have used XSLT 1.0 as the original code used that version although with XSLT 2/3 and positional grouping using for-each-group and/or a key computing the "pag" a "column" element belongs to might be more efficient:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="cols-per-table" as="xs:integer" select="10"/>
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:key name="page-no" match="/*/*/*">
<xsl:variable name="index" as="xs:integer">
<xsl:number/>
</xsl:variable>
<xsl:sequence select="($index - 1) idiv $cols-per-table + 1"/>
</xsl:key>
<xsl:template match="/">
<xsl:for-each-group select="*/*[1]/*" group-adjacent="(position() - 1) idiv $cols-per-table">
<xsl:apply-templates select="." mode="page">
<xsl:with-param name="page-no" select="position()" tunnel="yes"/>
</xsl:apply-templates>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="*" mode="page">
<xsl:param name="page-no" tunnel="yes"/>
<table border="1">
<thead>
<xsl:apply-templates select="current-group()" mode="th"/>
</thead>
<tbody>
<xsl:apply-templates select="/*/*"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/*" mode="th">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="/*/*">
<xsl:param name="page-no" tunnel="yes"/>
<tr>
<xsl:apply-templates select="key('page-no', $page-no, .)"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYjr/4
At https://xsltfiddle.liberty-development.net/jyRYYjr/5 I have implemented a pure XSLT 3 approach solely using for-each-group and storing the "pages" and "columns" in a map.

XSLT 2.0: Issues creating HTML table with dynamic rows & columns

I'm creating an HTML table that is based on dynamic columns(Hostname) and rows(VLAN). I'm running into an issue after the first position data(1 row for all hosts) in the is written to the table; selects the 2nd position data just fine, but the $vCol variable takes it back to the first line of the $vCols variable.
Appreciate any direction you can offer. Thanks
XSLT-2.0 code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:key name="kHostNameByValue" match="Hostname" use="."/>
<xsl:key name="kVLAN" match="Hostname" use="."/>
<xsl:variable name="vCols" select="/*/*/Hostname[generate-id()=generate-id(key('kHostNameByValue',.)[1])]"/>
<xsl:variable name="vMaxRows">
<xsl:for-each select="$vCols">
<xsl:sort data-type="number" order="descending" select="count(key('kVLAN', .))"/>
<xsl:if test="position() = 1">
<xsl:value-of select="count(key('kVLAN', .))"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="DocumentRoot">
<table border="1">
<!-- Print out column headings by Hostname -->
<tr>
<xsl:apply-templates select="$vCols"/>
</tr>
<!-- Print out VLANs by Hostname -->
<xsl:for-each-group select="(//Row)[not(position() > $vMaxRows)]" group-by="VLAN">
<tr>
<xsl:variable name="vPos" select="position()"/>
<!-- Issue on 2nd position when $vCols goes back to 1st hostname at line 3 -->
<xsl:for-each select="$vCols">
<td>
<xsl:value-of select="..[$vPos]/VLAN"/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
<xsl:template match="Hostname">
<td>
<b>
<xsl:value-of select="." />
</b>
</td>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Here's the sample XML data.
<?xml version="1.0" encoding="UTF-8"?>
<DocumentRoot>
<Row>
<Hostname>switch-1</Hostname>
<HostIP>10.29.178.102</HostIP>
<VLAN>10</VLAN>
<VLANName>VLAN-10</VLANName>
</Row>
<Row>
<Hostname>switch-1</Hostname>
<HostIP>10.29.178.102</HostIP>
<VLAN>500</VLAN>
<VLANName>VLAN-500</VLANName>
</Row>
<Row>
<Hostname>switch-2</Hostname>
<HostIP>10.29.178.103</HostIP>
<VLAN>11</VLAN>
<VLANName>VLAN-11</VLANName>
</Row>
<Row>
<Hostname>switch-2</Hostname>
<HostIP>10.29.178.103</HostIP>
<VLAN>501</VLAN>
<VLANName>VLAN-500</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>15</VLAN>
<VLANName>VLAN-15</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>25</VLAN>
<VLANName>VLAN-25</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>35</VLAN>
<VLANName>VLAN-35</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>45</VLAN>
<VLANName>VLAN-45</VLANName>
</Row>
<Row>
<Hostname>switch-3</Hostname>
<HostIP>10.29.170.1</HostIP>
<VLAN>55</VLAN>
<VLANName>VLAN-55</VLANName>
</Row>
</DocumentRoot>
Output (Actual and desired):
Firstly, I think your maxRows variable can be simplified to this
<xsl:variable name="maxRows" select="max(//Row/count(key(hostNameByValue, Hostname)))" />
Where I have defined hostNameByValue key like so:
<xsl:key name="hostNameByValue" match="Row" use="Hostname"/>
You can also use distinct-values to get the distinct column names
<xsl:variable name="cols" select="distinct-values(//Row/Hostname)" />
So, assuming $rowNum is the current number (within a <xsl:for-each select="1 to $maxRows"> block, the code to get the current cell value would be this
<xsl:for-each select="$cols">
<th><xsl:value-of select="key('hostNameByValue', ., $doc)[position() = $rowNum]/VLAN"/></th>
</xsl:for-each>
(Where $doc is a reference to the initial XML document, because within the xsl:for-each is now a sequence of atomic values)
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="hostNameByValue" match="Row" use="Hostname"/>
<xsl:variable name="cols" select="distinct-values(//Row/Hostname)" />
<xsl:variable name="maxRows" select="max(//Row/count(key('hostNameByValue', Hostname)))" />
<xsl:variable name="doc" select="/" />
<xsl:template match="DocumentRoot">
<table>
<tr>
<xsl:for-each select="$cols">
<th><xsl:value-of select="."/></th>
</xsl:for-each>
</tr>
<xsl:for-each select="1 to $maxRows">
<xsl:variable name="rowNum" select="position()"/>
<tr>
<xsl:for-each select="$cols">
<th><xsl:value-of select="key('hostNameByValue', ., $doc)[position() = $rowNum]/VLAN"/></th>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/6r5Gh3N
I think using a group by might make this more complicated than it needs to be. Basically for each row you need to iterate over all columns and output the cell, if it exists, or an empty cell otherwise. This means you should iterate over an index rather then row elements:
<xsl:for-each select="1 to $numRows">
<xsl:variable name="rowIndex" select="position()" />
<tr>
<xsl:for-each select="$vCols">
<xsl:variable name="cell" select="//Row[string(Hostname) = .][position() = $rowIndex]" />
<xsl:apply-templates select="$cell">
<xsl:if test="not($cell)">
<td></td>
</xsl:if>
</xsl:for-each>
<tr>
</xsl:for.each>
I don't think in XSLT 2 or 3, once you use for-each-group, you need any of the keys, you can just store the grouping result and then process it, for instance to store the grouping result as XML in XSLT 2 or 3 you could use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="DocumentRoot">
<table>
<xsl:variable name="cols" as="element(col)*">
<xsl:for-each-group select="Row" group-by="Hostname">
<col name="{current-grouping-key()}">
<xsl:sequence select="current-group()"/>
</col>
</xsl:for-each-group>
</xsl:variable>
<thead>
<tr>
<xsl:for-each select="$cols">
<th>{#name}</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:variable name="rows" select="max($cols!count(Row))"/>
<xsl:for-each select="1 to $rows">
<xsl:variable name="row" select="."/>
<tr>
<xsl:for-each select="$cols">
<td>{Row[$row]/VLAN}</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94rmq6Q/3 is XSLT 3 with the ! map operator and the text value templates but https://xsltfiddle.liberty-development.net/94rmq6Q/4 rewrites that as XSLT 2 with value-of instead.
Or in XSLT 3 you could store the grouping result in a sequence of arrays:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="DocumentRoot">
<table>
<xsl:variable name="cols" as="array(element(Row))*">
<xsl:for-each-group select="Row" group-by="Hostname">
<xsl:sequence select="array{ current-group() }"/>
</xsl:for-each-group>
</xsl:variable>
<thead>
<tr>
<xsl:for-each select="$cols">
<th>{?1/Hostname}</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:variable name="rows" select="max($cols!array:size(.))"/>
<xsl:for-each select="1 to $rows">
<xsl:variable name="row" select="."/>
<tr>
<xsl:for-each select="$cols">
<td>{if ($row le array:size(.))
then .($row)/VLAN
else ()}</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94rmq6Q/2

xslt sorting not working

basically i want to sort data by level and i'm getting an error. I'm a newbie to xslt. i tried the below code but not working.
here is my xml file.
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="XSLTEx1WT.xsl"?>
<Modules>
<module code="CSE2041">
<name>Web Technologies II</name>
<credit>3</credit>
<level>2</level>
</module>
<module code="CSE2031Y">
<name>Object Oriented Software Development</name>
<credit>6</credit>
<level>2</level>
</module>
<module code="CSE1041">
<name>Web Technologies I</name>
<credit>3</credit>
<level>1</level>
</module>
</Modules>
here is my xsl file
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:variable name="deptCredit" select="99"/>
<xsl:variable name="myBorder" select="1"/>
<xsl:template match="/">
<html>
<head>
<title>XSLT Exercise 1</title></head>
<body>
<table border="{$myBorder}">
<thead><tr><th>Module Name</th><th>No. of Credits</th><th>Level</th></tr></thead>
<tbody>
<xsl:apply-templates>
**<xsl:sort select="level" data-type="number"/>**
</xsl:apply-templates>
<tr><td colspan="3">Departmental credits needed: <xsl:value-of select="$deptCredit"/></td></tr>
<tr><td colspan="3">Percentage cleared: <strong> <xsl:value-of select="format-number(sum(//credit/text()) div $deptCredit,'##.##%')"/></strong>
</td></tr>
</tbody>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="module">
<xsl:apply-templates select="#code"/>
<tr><xsl:apply-templates select="*"/></tr>
</xsl:template>
<xsl:template match="name">
<xsl:call-template name="sjName"/>
</xsl:template>
<xsl:template match="#code">
<tr style="background-color:silver;">
<td colspan="3" style="text-align:center;">
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
<xsl:template match="credit">
<td style="color:blue;"><strong><xsl:value-of select="text()"/></strong></td>
</xsl:template>
<xsl:template match="level">
<td style="color:blue;"><strong><xsl:value-of select="text()"/></strong></td>
</xsl:template>
<xsl:template name="sjName">
<xsl:choose>
<xsl:when test="contains(../#code,'Y')">
<td style="color:orange;font-weight:bold;"><xsl:value-of select="."/></td>
</xsl:when>
<xsl:otherwise>
<td style="color:lime;font-weight:bold;"><xsl:value-of select="."/></td>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
i have tried the above but its not working
You simply need to change this:
<xsl:apply-templates>
<xsl:sort select="level" data-type="number"/>
</xsl:apply-templates>
to this:
<xsl:apply-templates select="Modules/module">
<xsl:sort select="level" data-type="number"/>
</xsl:apply-templates>

sorting decimals with XSLT

I am trying to sort some XML data, i have followed W3s tutorials, but my code doesn't work, whats wrong?
<xsl:for-each select="garage/car[colour='red']">
<xsl:apply-templates>
<xsl:sort select="number(price)" order="descending" data-type="number" />
</xsl:apply-templates>
<tr>
<td><xsl:value-of select="make"/></td>
<td><xsl:value-of select="model"/></td>
<td><xsl:value-of select="price"/></td>
</tr>
</xsl:for-each>
XML example~:
<garage>
<car>
<make>vw</make>
<model>golf</model>
<color>red</color>
<price>5.99</price>
</car>
<car>
<make>ford</make>
<model>focus</model>
<color>black</color>
<price>3.66</price>
</car>
<car>
<make>honda</make>
<model>civic</model>
<color>red</color>
<price>15.99</price>
</car>
</garage>
So you want to show all the red cars in descending price order?
It helps if you are consistent with your spelling of color/colour! I am British and feel your pain!
Also you are mixing up the for-each and apply-templates (both work)
apply-templates method:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" indent="yes" />
<xsl:template match="/">
<xsl:apply-templates select="garage/car[color='red']">
<xsl:sort select="number(price)" order="descending" data-type="number" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="car[color='red']">
<tr>
<td>
<xsl:value-of select="make" />
</td>
<td>
<xsl:value-of select="model" />
</td>
<td>
<xsl:value-of select="price" />
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
for-each method:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" indent="yes" />
<xsl:template match="/">
<xsl:for-each select="garage/car[color='red']">
<xsl:sort select="number(price)" order="descending" data-type="number" />
<tr>
<td>
<xsl:value-of select="make" />
</td>
<td>
<xsl:value-of select="model" />
</td>
<td>
<xsl:value-of select="price" />
</td>
</tr>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Line Break in XSLT for a comma separated value

I am new to XSLT and I am trying the following:
I have the following:
<TR> <TD> Name: </TD> <TD><xsl:value-of select="ZNAME"/> </TD> </TR>
which returns
ADAM,BRIAN,CHARLIE,DAVID
How I can make this return:
ADAM
BRIAN
CHARLIE
DAVID
Please suggest.
This transformation:
<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="ZNAME" name="tokenize">
<xsl:param name="pText" select="concat(., ',')"/>
<xsl:if test="string-length($pText)>0">
<xsl:value-of select="substring-before($pText, ',')"/>
<br />
<xsl:call-template name="tokenize">
<xsl:with-param name="pText"
select="substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<ZNAME>ADAM,BRIAN,CHARLIE,DAVID</ZNAME>
produces the wanted, correct result:
ADAM<br/>BRIAN<br/>CHARLIE<br/>DAVID<br/>
which when viewed in a browser is displayed like:
ADAMBRIANCHARLIEDAVID
If you need a solution that doesn't output a trailing <br/> element, try this:
<TR> <TD> Name: </TD> <TD><xsl:apply-templates select="ZNAME"/> </TD> </TR>
<xsl:template match="ZNAME" name="convertcommas">
<xsl:param name="text" select="."/>
<xsl:value-of select="substring-before(concat($text,','),',')" />
<xsl:if test="contains($text,',')">
<br />
<xsl:call-template name="convertcommas">
<xsl:with-param name="text" select="substring-after($text,',')" />
</xsl:call-template>
</xsl:if>
</xsl:template>
The concat($text,',') makes sure the substring-before sees at least one comma, thereby outputting the string on it's own if there were no commas originally.