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>
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
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>
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>
I have these trees, one with this structure /cars/car and the second /maker/cars/car. The first one has a reference to the id of the second list of cars.
<xsl:template match="t:cars/t:car">
<tr>
<td>
<xsl:if test="position()=1">
<b><xsl:value-of select="../#name"/><xsl:text> </xsl:text></b>
</xsl:if>
</td>
</tr>
I have this, it was filled in with a for loop I learn after a bit that I could't do it.
This is what it was before:
<xsl:template match="t:cars/t:car">
<tr>
<td>
<xsl:if test="position()=1">
<b><xsl:value-of select="../#name"/><xsl:text> </xsl:text></b>
</xsl:if>
<xsl:for-each select="/t:root/t:maker/t:car">
<xsl:if test="t:root/t:maker/#id = #ref">
<xsl:value-of select="#title"/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
sample:
auto>
<maker type="toyota">
<car name="prius" id="1"/>
</maker>
<cars name="My Collection">
<car ref="1" />
</cars>
This simple 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:key name="kCarById" match="maker/car" use="#id"/>
<xsl:template match="/*">
<table>
<xsl:apply-templates/>
</table>
</xsl:template>
<xsl:template match="cars/car">
<tr>
<td>
<b>
<xsl:value-of select="key('kCarById', #ref)/#name"/>
</b>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (the provided one, just extended a little):
<auto>
<maker type="toyota">
<car name="prius" id="1"/>
</maker>
<maker type="honda">
<car name="accord" id="2"/>
</maker>
<maker type="benz">
<car name="mercedes" id="3"/>
</maker>
<cars name="My Collection">
<car ref="2" />
<car ref="3" />
</cars>
</auto>
produces the wanted, correct result:
<table>
<tr>
<td>
<b>accord</b>
</td>
</tr>
<tr>
<td>
<b>mercedes</b>
</td>
</tr>
</table>
Explanation: Appropriate use of keys.
The XML:
<data>
<table id="returns">
<each>
<name>1 year</name>
<value>17.01062531999216</value>
</each>
<each>
<name>3 years</name>
<value>18.01062531999216</value>
</each>
<each>
<name>5 years</name>
<value>21.01062531999216</value>
</each>
<each>
<name>Since inception</name>
<value>12.01062531999216</value>
</each>
</table>
</data>
The XSL I have been trying:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<tr>
<xsl:for-each select="data/table[#id='returns']/each">
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="value"/></td>
<xsl:if test="//each[position() mod 2 = 0]">
<xsl:text disable-output-escaping="yes"></tr><tr></xsl:text>
</xsl:if>
</xsl:for-each>
</tr>
</xsl:template>
</xsl:stylesheet>
The result I want:
<table>
<tr>
<td>1 year</td>
<td>17.01062531999216</td>
<td>3 years</td>
<td>18.01062531999216</td>
</tr>
<tr>
<td>5 years</td>
<td>21.01062531999216</td>
<td>Since inception</td>
<td>12.01062531999216</td>
</tr>
</table>
I'm embarrassed to say how long I have been working on this. Suffice to say the brute force, try-everything-multiple-times technique didn't work.
A shorter and correct 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:template match="table">
<table>
<xsl:apply-templates/>
</table>
</xsl:template>
<xsl:template match="each[position() mod 2 = 1]">
<tr>
<xsl:apply-templates select="(.|following-sibling::each[1])/*"/>
</tr>
</xsl:template>
<xsl:template match="name|value">
<td><xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="each[position() mod 2 = 0]"/>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<data>
<table id="returns">
<each>
<name>1 year</name>
<value>17.01062531999216</value>
</each>
<each>
<name>3 years</name>
<value>18.01062531999216</value>
</each>
<each>
<name>5 years</name>
<value>21.01062531999216</value>
</each>
<each>
<name>Since inception</name>
<value>12.01062531999216</value>
</each>
</table>
</data>
the wanted, correct result is produced:
<table>
<tr>
<td>1 year</td>
<td>17.01062531999216</td>
<td>3 years</td>
<td>18.01062531999216</td>
</tr>
<tr>
<td>5 years</td>
<td>21.01062531999216</td>
<td>Since inception</td>
<td>12.01062531999216</td>
</tr>
</table>
To render 2 cells per row.
Select every second node starting with the first. For these, render a row containing self and the following cell. If the last row does not contain 2 each elements, finish the row with empty cells.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="table">
<table>
<xsl:apply-templates select="each[1 = position() mod 2]"/>
</table>
</xsl:template>
<xsl:template match="each">
<tr>
<xsl:for-each select=". | following-sibling::each[1]" >
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="value"/></td>
</xsl:for-each>
<xsl:if test="not(following-sibling::each)">
<td/>
<td/>
</xsl:if>
</tr>
</xsl:template>
</xsl:stylesheet>