Please help with basic XSLT template for creating columns for each item.
INPUT XML:
<list>
<item>
<name>John</name>
<image>John Picture</image>
</item>
<item>
<name>Bob</name>
<image>Bob Picture</image>
</item>
</list>
OUTPUT HTML:
<table>
<tr>
<td>John</td>
<td>Bob</td>
</tr>
<tr>
<td>John Picutre</td>
<td>Bob Picture</td>
</tr>
</table>
Thank you very much in advance
If you want a column for each item element, then you should start off by selecting the elements under only the first item elements, as these will represent the start of each row
<xsl:for-each select="item[1]/*">
Then, to build the row, get the relevant element under all item element which has the same name as the one currently selected
<xsl:apply-templates select="../../item/*[name() = name(current())]" />
Although it might be easier if you define a key like so...
<xsl:key name="items" match="item/*" use="name()" />
Then you get the elements with the same name like so:
<xsl:apply-templates select="key('items', name())" />
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:key name="items" match="item/*" use="name()" />
<xsl:template match="list">
<table>
<xsl:for-each select="item[1]/*">
<tr>
<xsl:apply-templates select="key('items', name())" />
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="item/*">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
</xsl:stylesheet>
This assumes all elements are present under each item (well, at least under the first item).
You need to post the XSLT which you write for achive the result, below is the code you can use:
Final Updated Script:
<?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="1.0">
<xsl:output indent="yes"/>
<xsl:template match="list">
<table>
<tr>
<xsl:for-each select="item/name">
<td>
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
<tr>
<xsl:for-each select="item/image">
<td>
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
Related
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
This transformable HTML5:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<table border="1">
<caption>Complex Table</caption>
<tbody>
<tr>
<th>Title</th>
<th>Volume</th>
<th>Chapter</th>
<th>Stds.</th>
<th>Dept.</th>
</tr>
<tr>
<td rowspan="6">STEM</td>
<td rowspan="1">1</td>
<td rowspan="2">1</td>
<td>1 to 10</td>
<td rowspan="2">Biology</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>20 to 30</td>
</tr>
<tr>
<td rowspan="1">3</td>
<td rowspan="1">2</td>
<td>40 to 60</td>
<td rowspan="1">Chemistry</td>
</tr>
<tr>
<td>4</td>
<td>3</td>
<td>70 to 80</td>
<td>Physics</td>
</tr>
<tr>
<td rowspan="4">5</td>
<td rowspan="1">4</td>
<td>80 to 120</td>
<td rowspan="1">Math</td>
</tr>
<tr>
<td rowspan="1">5</td>
<td>120 to 135</td>
<td rowspan="1">Geometry</td>
</tr>
</tbody>
</table>
<table border="1">
<caption>Simpler Table</caption>
<tbody>
<tr>
<th>Title</th>
<th>Volume</th>
<th>Chapter</th>
<th>Stds.</th>
<th>Dept.</th>
</tr>
<tr>
<td colspan="1" rowspan="3">Kinesiology</td>
<td>1</td>
<td>1</td>
<td>A to C</td>
<td>Strength</td>
</tr>
<tr>
<td>2</td>
<td>2 to 3</td>
<td>D to H</td>
<td>Agility</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
<td>I to X</td>
<td>Flexibility</td>
</tr>
</tbody>
</table>
<table border="1">
<caption>Simplest Table</caption>
<tbody>
<tr>
<th>Title</th>
<th>Volume</th>
<th>Chapter</th>
<th>Stds.</th>
<th>Dept.</th>
</tr>
<tr>
<td>Skills</td>
<td>1</td>
<td>1</td>
<td>A to C</td>
<td>Keyboard</td>
</tr>
</tbody>
</table>
</body>
</html>
This desired output (if you view the rendered HTML, you can see the pattern of data wanted):
<?xml version="1.0" encoding="UTF-8"?>
<production>
<book title="STEM" volume="1"/>
<book title="STEM" volume="2"/>
<book title="STEM" volume="3"/>
<book title="STEM" volume="4"/>
<book title="STEM" volume="5"/>
<book title="Kinesiology" volume="1"/>
<book title="Kinesiology" volume="2"/>
<book title="Kinesiology" volume="3"/>
<book title="Skills" volume="1"/>
</production>
The not quite working transform:
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates/>
</catalog>
</xsl:template>
<xsl:template match="text()"/>
<!-- multi-volume edition -->
<xsl:template match="table">
<xsl:variable name="title" select="descendant::td[1]"/>
<xsl:variable name="context-td" select="."/>
<!-- the following needs work -->
<xsl:for-each select="descendant::tr/td[1][matches(.,'\d+$')]">
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
</xsl:for-each>
</xsl:template>
<!-- single-volume edition -->
<xsl:template match="table[count(descendant::tr) < 3]">
<book>
<xsl:attribute name="title" select="descendant::td[1]"/>
<xsl:attribute name="volume" select="descendant::tr[2]/td[2]"/>
</book>
</xsl:template>
</xsl:stylesheet>
The xpath in for-each needs work. I've tried various axis but haven't found one that works across all use cases.
Couldn't this be simply:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="html/body/table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:variable name="title" select="tbody/tr[2]/td[1]"/>
<xsl:for-each select="tbody/tr[2]/td[2] | tbody/tr[position() > 2]/td[1]">
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Oops, I see there is a problem with volume 5 of STEM being listed twice - hold on...
No, I don't see a simple solution to this. I suspect you'd have to drill down into the table's structure, taking preceding rowspans into consideration - somewhat similar to:
Please suggest for XSLT code for Table rowspan and colspan issues
Edit:
Ok, I believe this should work:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="html/body/table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:apply-templates select="tbody/tr[2]/td[2]">
<xsl:with-param name="title" select="tbody/tr[2]/td[1]" tunnel="yes"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="td">
<xsl:param name="title" tunnel="yes"/>
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
<xsl:variable name="rowspan" select="if(#rowspan) then #rowspan else 1" />
<xsl:apply-templates select="parent::tr/following-sibling::tr[number($rowspan)]/td[1]"/>
</xsl:template>
</xsl:stylesheet>
Test, applied to a modified input in the form of:
http://xsltransform.net/94hvTz1/2
I tried it with grouping:
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="//table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:for-each-group select="tbody/tr[position() gt 1]/td[1]" group-by="../../(tr[2]/td[2] | tr[position() gt 2]/td[1])">
<book title="{.}" volume="{current-grouping-key()}"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Would this help by any chance(little changes made to michael.hor257k's answer) :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="html/body/table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:variable name="title" select="tbody/tr[2]/td[1]"/>
<xsl:variable name="table-id" select="generate-id()"/>
<xsl:for-each select="tbody/tr[2]/td[2] | tbody/tr[position() > 2]/td[1]">
<xsl:variable name="curr-td" select="."/>
<xsl:if test="not(exists(following::tr[td[1][generate-id(../../..) = $table-id and . = $curr-td]]))">
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
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
I would like get iteration number in XSL, but in my foreach i have a condition and i woulde like write my iteration, but only for true in my condition.
I use "position()" and i would like write position in a table.
<xsl:for-each ... >
<xsl:if test="Jordan = $name">
<xsl:value-of select="position() = 1">
</xsl:if>
</xsl:for-each>
After i get this 1-3, I would like 1-2 (only success condition)
MY XML :
<root>
<user>
<name>Jordan</name>
<forename>Michael</forename>
</user>
<user>
<name>Braun</name>
<forename>Steve</forename>
</user>
<user>
<name>Jordan</name>
<forename>David</forename>
</user>
</root>
the expected output a HTML table
<table>
<tr>
<td>1</td>
<td>Michael</td>
</tr>
<tr>
<td>2</td>
<td>David</td>
</tr>
</table>
EDIT: The OP has altered their question to provide new information. This requires an answer different than the original one.
If the goal is to output info only for those <user> elements whose <name> child is "Jordan", this XSLT should do the trick:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<table>
<xsl:apply-templates select="*[name = 'Jordan']/forename"/>
</table>
</xsl:template>
<xsl:template match="forename">
<tr>
<td>
<xsl:value-of select="position()"/>
</td>
<td>
<xsl:apply-templates/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
EDIT: This answer is left for posterity.
Rather than looking at this as an "incrementing loop problem" (which is more of a procedural methodology), I would look at this problem -- at least, as it pertains to XSLT -- as a grouping problem. Doing this in XSLT 1.0 means using Muenchian Grouping.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kForenames" match="forename" use="."/>
<xsl:template match="/*">
<table>
<xsl:apply-templates
select="*/forename[generate-id() =
generate-id(key('kForenames', .)[1])]"/>
</table>
</xsl:template>
<xsl:template match="forename">
<tr>
<td>
<xsl:value-of select="position()"/>
</td>
<td>
<xsl:apply-templates/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
...is applied against the provided XML:
<root>
<user>
<name>Jordan</name>
<forename>Michael</forename>
</user>
<user>
<name>Braun</name>
<forename>David</forename>
</user>
<user>
<name>Jordan</name>
<forename>David</forename>
</user>
</root>
...the wanted result is produced:
<table>
<tr>
<td>1</td>
<td>Michael</td>
</tr>
<tr>
<td>2</td>
<td>David</td>
</tr>
</table>
<xsl:variable name="mergedData">
<xsl:for-each ... >
<xsl:if test="Jordan = $name">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="msxsl:node-set($mergedData)/*">
<xsl:value-of select="position()"/>
</xsl:for-each>
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>