Advanced xsl loop - xslt

I would like to build a table for each JOB of type T. This table must have 2 columns (LPATH and RPATH). LPATH1 must be on the same row as RPATH1, etc...
<ROOT>
<FOLDER>
<JOB type="T">
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/aL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/aR" />
<VARIABLE NAME="%%FTP-LPATH2" VALUE="/path/to/bL" />
<VARIABLE NAME="%%FTP-RPATH2" VALUE="/path/to/bR" />
</JOB>
<JOB type="O" />
<JOB type="T">
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/eL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/eR" />
</JOB>
</FOLDER>
<FOLDER>
<JOB type="O" />
<JOB type="T">
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/cL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/cR" />
<VARIABLE NAME="%%FTP-LPATH2" VALUE="/path/to/dL" />
<VARIABLE NAME="%%FTP-RPATH2" VALUE="/path/to/dR" />
<VARIABLE NAME="%%FTP-LPATH3" VALUE="/path/to/fL" />
<VARIABLE NAME="%%FTP-RPATH3" VALUE="/path/to/fR" />
</JOB>
<JOB type="T">
<VARIABLE NAME="%%FTP-RPATH5" VALUE="/path/to/kR" />
<VARIABLE NAME="%%FTP-LPATH1" VALUE="/path/to/gL" />
<VARIABLE NAME="%%FTP-RPATH1" VALUE="/path/to/gR" />
<VARIABLE NAME="%%FTP-LPATH2" VALUE="/path/to/hL" />
<VARIABLE NAME="%%FTP-RPATH2" VALUE="/path/to/hR" />
<VARIABLE NAME="%%FTP-LPATH3" VALUE="/path/to/iL" />
<VARIABLE NAME="%%FTP-RPATH3" VALUE="/path/to/iR" />
<VARIABLE NAME="%%FTP-LPATH4" VALUE="/path/to/jL" />
<VARIABLE NAME="%%FTP-RPATH4" VALUE="/path/to/jR" />
<VARIABLE NAME="%%FTP-LPATH5" VALUE="/path/to/kL" />
</JOB>
<JOB type="O" />
</FOLDER>
</ROOT>
to obtain something like this:
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<tr>
<td>/path/to/aL</td>
<td>/path/to/aR</td>
</tr>
<tr>
<td>/path/to/bL</td>
<td>/path/to/bR</td>
</tr>
...
number of RPATHx/LPATHx couple is not determined
What could you suggest ?
Best regards

If they always come as left/right pairs, 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" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/JOB">
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:for-each select="VARIABLE[contains(#NAME, 'LPATH')]">
<tr>
<td>
<xsl:value-of select="#VALUE"/>
</td>
<td>
<xsl:value-of select="following-sibling::VARIABLE[1]/#VALUE"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
or even simpler:
<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="/JOB">
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:for-each select="VARIABLE[position() mod 2 = 1]">
<tr>
<td>
<xsl:value-of select="#VALUE"/>
</td>
<td>
<xsl:value-of select="following-sibling::VARIABLE[1]/#VALUE"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Added:
If they can come out of order, as shown in your updated example, then try:
XSLT 2.0
<xsl:stylesheet version="2.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:key name="r" match="VARIABLE[starts-with(#NAME, '%%FTP-RPATH')]" use="substring-after(#NAME, '%%FTP-RPATH')" />
<xsl:template match="/ROOT">
<root>
<xsl:for-each select="FOLDER/JOB[#type='T']">
<xsl:variable name="job" select="." />
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:for-each select="VARIABLE[starts-with(#NAME, '%%FTP-LPATH')]">
<tr>
<td>
<xsl:value-of select="#VALUE"/>
</td>
<td>
<xsl:value-of select="key('r', substring-after(#NAME, '%%FTP-LPATH'), $job)/#VALUE"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/6pS26mt
Note that this still assumes that they come in pairs - or at least that the LPATH value of the pair will always be present.

Assuming VARIABLE/#NAME always contain LPATHnnn and RPATHnnn as their name :
<xsl:template match="/">
<html>
<table>
<tr>
<th>LPATH</th>
<th>RPATH</th>
</tr>
<xsl:apply-templates select="JOB/VARIABLE[contains(#NAME,'LPATH')]"/>
</table>
</html>
</xsl:template>
<xsl:template match="VARIABLE">
<tr>
<th><xsl:value-of select="#VALUE"/></th>
<xsl:variable name="Rname" select="replace(#NAME,'LPATH','RPATH')"/>
<th><xsl:value-of select="//JOB/VARIABLE[#NAME=$Rname]/#VALUE"/></th>
</tr>
</xsl:template>
See it working here : https://xsltfiddle.liberty-development.net/pNmC4HG

Related

Creating Columns in XSLT

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>

XSL combine two loops on same level

I have this structure in my xml:
<zoo>
<species name="bird" />
<animal name="crane" />
<animal name="duck" />
<species name="fish" />
<animal name="dolphin" />
<animal name="goldfish" />
</zoo>
which I want to transform into something like this:
<table>
<tr><td> <b>bird</b> </td></tr>
<tr><td> crane </td></tr>
<tr><td> duck </td></tr>
</table>
<table>
<tr><td> <b>fish</b> </td></tr>
<tr><td> dolphin </td></tr>
<tr><td> goldfish </td></tr>
</table>
How can I make this work? I tried using nested for:each'es, but that obviously doesn't work since the nodes are not nested.
Assuming an XSLT 2.0 processor like Saxon 9 or XmlPrime you can use for-each-group group-starting-with="species":
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<htmt>
<head>
<title>group-starting-with</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="zoo">
<xsl:for-each-group select="*" group-starting-with="species">
<table>
<xsl:apply-templates select="current-group()"/>
</table>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="species">
<tr>
<th>
<xsl:value-of select="#name"/>
</th>
</tr>
</xsl:template>
<xsl:template match="animal">
<tr>
<td><xsl:value-of select="#name"/></td>
</tr>
</xsl:template>
</xsl:transform>
Online at http://xsltransform.net/nc4NzRc/1.

XSLT and Complex Xpath

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>

How do you link two elements together when they have the same name in xslt?

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.

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>