how locate an element in source xml file dynamically in a loop - xslt

if we have the following xml input:
<root xmlns:ns="http://www.blabla">
<ns:element1 attribute="attr1">10</ns:element1>
<ns:element1 attribute="attr2">20</ns:element1>
<ns:element2 attribute="attr1">30</ns:element1>
<ns:element2 attribute="attr2">40</ns:element1>
</root>
how can we locate each element inside a for-each in xslt?
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Column 1</th>
<th style="text-align:left">Column 2</th>
</tr>
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<tr>
<td>
<xsl:value-of select="???what should be here???[lower-case(#attribute)='attr0']"/>
</td>
<td>
<xsl:value-of select="???same question???[lower-case(#attribute)='attr1']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
Please note that I want to follow this procedure (if possible) as the input is very dynamic and we don't always get all elements in every row.
I appreciate your help.

The expression you want is this...
<xsl:value-of select="*[local-name() = $name][lower-case(#attribute)='attr1']"/>
... Except this will fail with an error along the lines of Required item type of context item for the child axis is node(); supplied expression (.) has item type xs:integer, due to the code being executed in the context of the xsl:for-each on atomic values. To get around this, you will need to save a reference to the child elements in a variable before the xsl:for-each.
Try this XSLT
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
</tr>
<xsl:variable name="children" select="*" />
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<tr>
<td>
<xsl:value-of select="$children[local-name() = $name][lower-case(#attribute)='attr1']"/>
</td>
<td>
<xsl:value-of select="$children[local-name() = $name][lower-case(#attribute)='attr2']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Or slightly better, to reduce code duplication...
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
</tr>
<xsl:variable name="children" select="*" />
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<xsl:variable name="element" select="$children[local-name() = $name]"/>
<tr>
<td>
<xsl:value-of select="$element[lower-case(#attribute)='attr1']"/>
</td>
<td>
<xsl:value-of select="$element[lower-case(#attribute)='attr2']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Note, I would really consider changing your input XML if you have control over it. Numbering elements using the element name makes it harder to manipulate. Ideally you would do this instead...
<ns:element num="1" attribute="attr1">10</ns:element>

If you dont have control try to take count of the elements and run the loop as per count like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://www.blabla">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Column 1</th>
<th style="text-align:left">Column 2</th>
</tr>
<xsl:variable name="all-element" select="count(//*:element)"/>
<xsl:for-each select="*[concat('ns:element', (1 to $all-element))]">
<xsl:message select="."/>
<tr>
<td>
<xsl:value-of select="#attribute"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Related

Adding rowspan via XSL

I have the following xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Loci>
<Locus>
<Id>MTBC1726</Id>
<Alleles>
<Allele>
<Name>1.0</Name>
<Description>No annotation provided</Description>
</Allele>
<Allele>
<Name>2.0</Name>
<Description>No annotation provided</Description>
</Allele>
</Alleles>
</Locus>
<Locus>
<Id>MTBC3142</Id>
<Alleles>
<Allele>
<Name>1.0</Name>
<Description>No annotation provided</Description>
</Allele>
</Alleles>
</Locus>
</Loci>
And I want to create the following result:
which, in HTML, would look like this:
<html>
<body>
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<tr>
<td rowspan="2">MTBC1726</td>
<td>1.0</td>
<td >No annotation provided</td>
</tr>
<tr>
<td>2.0</td>
<td >No annotation provided</td>
</tr>
<tr>
<td >MTBC3142</td>
<td>1.0</td>
<td >No annotation provided</td>
</tr>
</table>
</body>
</html>
I have created the following XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<xsl:for-each select="Loci/Locus">
<tr>
<td><xsl:value-of select="Id"/></td>
<xsl:for-each select="Alleles/Allele">
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="Description"/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
but that produces a table like this:
So, my question is: how do I add the rowspan attribute with a count based on the number of <Allele> nodes?
How about this:
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<xsl:for-each select="Loci/Locus">
<xsl:for-each select="Alleles/Allele">
<tr>
<xsl:if test="position() = 1">
<td rowspan="{last()}">
<xsl:value-of select="ancestor::Locus[1]/Id"/>
</td>
</xsl:if>
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="Description"/></td>
</tr>
</xsl:for-each>
</xsl:for-each>
</table>
The idea here is that within the inner for-each you can use position() to see whether you're on the first Allele for this Locus, and last() to give the number of Allele elements that the current Locus contains. The ancestor::Locus[1] is because we need to extract the Locus Id at a point where the current context is its first Allele.
If you want to avoid adding rowspan="1" for single-allele loci, you'll have to use a conditional xsl:attribute instead of an attribute value template:
<xsl:if test="position() = 1">
<td>
<xsl:if test="last() > 1">
<xsl:attribute name="rowspan">
<xsl:value-of select="last()" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="ancestor::Locus[1]/Id"/>
</td>
</xsl:if>
You could use the XSL count() function to define a variable, and then output that variable as your attribute value:
<xsl:variable name="recordCount" select="count(Alleles/Allele)"/>
<td rowspan="{$recordCount}"><xsl:value-of select="Id"/></td>
You can count the number of Allele and use that value to create your rowspan. I also put in a check so that it didn't add a rowspan=1 for cases when there was only the one Allele
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Locus</th>
<th>Allele</th>
<th>Description</th>
</tr>
<xsl:for-each select="Loci/Locus">
<tr>
<td>
<xsl:if test="count(Alleles/Allele) > 1">
<xsl:attribute name="rowspan">
<xsl:value-of select="count(Alleles/Allele)"/>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="Id"/>
</td>
<xsl:for-each select="Alleles/Allele">
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="Description"/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>

Recursive variable

First XML
<?xml version="1.0"?>
<response>
<status>
<code>0</code>
</status>
<newsList>
<news>
<id>1</id>
<title>some</title>
<date>30.11.2011T00:00.00</date>
<shortText>some short text</shortText>
<important>LOW</important>
</news>
Second XML
<?xml version="1.0"?>
<response>
<status>
<code>0</code>
</status>
<newsList>
<news>
<id>1</id>
<text>
Some text here
</text>
</news>
The result should be dysplaing title date and short Text from the first XML and the text from the second XML.
Below the XSLT I got so far.
<xsl:template match="response">
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th align="left">Title</th>
<th align="left">shortText</th>
<th align="left">date</th>
<th align="left">text</th>
</tr>
<xsl:for-each select="newsList/news">
<tr>
<td><xsl:value-of select="title" /></td>
<td><xsl:value-of select="shortText" /></td>
<td><xsl:value-of select="date" /></td>
<td><xsl:value-of select="document('news-details.xml')//news[id=$id_news]/text"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
But this will always show the text from the news number 1.
I know is't not possible to update the vaulue but how can get it done?
Here is an example using a key:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" version="5.0"/>
<xsl:param name="url2" select="'news-details.xml'"/>
<xsl:variable name="doc2" select="document($url2, /)"/>
<xsl:key name="k1" match="news" use="id"/>
<xsl:template match="/">
<html>
<head>
<title>Example</title>
</head>
<body>
<table>
<thead>
<tr>
<th>Title</th>
<th>short text</th>
<th>date</th>
<th>text</th>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="//news"/>
</tbody>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="news">
<xsl:variable name="id" select="id"/>
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="shortText"/></td>
<td><xsl:value-of select="date"/></td>
<td>
<xsl:for-each select="$doc2">
<xsl:value-of select="key('k1', $id)/text"/>
</xsl:for-each>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="response">
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th align="left">Title</th>
<th align="left">shortText</th>
<th align="left">date</th>
<th align="left">text</th>
</tr>
<xsl:apply-templates select="newsList/news"/>
</table>
</xsl:template>
<xsl:template match="newsList/news">
<xsl:variable name="id_news" select="ID"/>
<tr>
<td><xsl:value-of select="title" /></td>
<td><xsl:value-of select="shortText" /></td>
<td><xsl:value-of select="date" /></td>
<td>
<xsl:apply-templates select="document('news-details.xml')//news[id=$id_news]/text"/>
</td>
</tr>
</xsl:template>
<xsl:template match="text">
<xsl:value-of select="."/>
</xsl:template>

XSLT variable not working

I have the following piece of XSLT code:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:variable name="condition">(coasts='Adriatic Sea')or(coasts='Mediterranean Sea')</xsl:variable>
<xsl:template match="cia">
<html>
<head></head>
<body>
<table border="1">
<tr>
<th>Country</th>
<th>Capital</th>
<th>Area</th>
<th>Population</th>
<th>Inflation rate</th>
</tr>
<xsl:for-each select="country">
<xsl:if test="{$condition}">
<tr>
<td>
<xsl:value-of select="#name"/>
</td>
<td>
<xsl:value-of select="#capital"/>
</td>
<td>
<xsl:value-of select="#total_area"/>
</td>
<td>
<xsl:value-of select="#population"/>
</td>
<td>
<xsl:value-of select="#inflation"/>
</td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
If put the conditional expression directly in the if element the code works fine, however, my problem is that when I assign the same conditional expression to the variable and then reference it in the if element it doesn't work. Am I doing something wrong? Is this possible?
Thanks!
There are multiple problems:
The brackets in <xsl:if test="{$condition}"> are unnecessary; use <xsl:if test="$condition">
Use the following xsl:variable construct:
<xsl:variable name="condition"
select="(coasts='Adriatic Sea')or(coasts='Mediterranean Sea')"/>
When you had the condition in your xsl:if the test was performed relative to each country. This is not the case in a top-level variable. The value of the variable is the result of the expression, not the expression itself. If you insist on a variable, then initialize it inside the loop.
See the following stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:template match="cia">
<html>
<head></head>
<body>
<table border="1">
<tr>
<th>Country</th>
<th>Capital</th>
<th>Area</th>
<th>Population</th>
<th>Inflation rate</th>
</tr>
<xsl:for-each select="country">
<xsl:variable name="condition"
select="(coasts='Adriatic Sea') or
(coasts='Mediterranean Sea')" />
<xsl:if test="$condition">
<tr>
<td>
<xsl:value-of select="#name" />
</td>
<td>
<xsl:value-of select="#capital" />
</td>
<td>
<xsl:value-of select="#total_area" />
</td>
<td>
<xsl:value-of select="#population" />
</td>
<td>
<xsl:value-of select="#inflation" />
</td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Input:
<cia>
<country name="test1" inflation="22">
<coasts>Adriatic Sea</coasts>
</country>
<country name="test2" inflation="7">
<coasts>No match</coasts>
</country>
</cia>
Output (only the first country passes the test):
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
<table border="1">
<tr>
<th>Country</th>
<th>Capital</th>
<th>Area</th>
<th>Population</th>
<th>Inflation rate</th>
</tr>
<tr>
<td>test1</td>
<td></td>
<td></td>
<td></td>
<td>22</td>
</tr>
</table>
</body>
</html>
Note that you don't really even need a separate condition; it's better to select just the desired elements in the first place. This loop produces the same output:
<xsl:for-each
select="country[(coasts='Adriatic Sea') or
coasts='Mediterranean Sea')]">
<tr>
<td>
<xsl:value-of select="#name" />
</td>
<td>
<xsl:value-of select="#capital" />
</td>
<td>
<xsl:value-of select="#total_area" />
</td>
<td>
<xsl:value-of select="#population" />
</td>
<td>
<xsl:value-of select="#inflation" />
</td>
</tr>
</xsl:for-each>
Replace:
<xsl:for-each select="country">
with
<xsl:apply templates select="country"/>
also, add these templates:
<xsl:template match="country"/>
<xsl:template match=
"country[coasts='Adriatic Sea'
or
coasts='Mediterranean Sea'
]">
<xsl:template match=
"country[coasts='Adriatic Sea'
or
coasts='Mediterranean Sea'
]">
<tr>
<td>
<xsl:value-of select="#name"/>
</td>
<td>
<xsl:value-of select="#capital"/>
</td>
<td>
<xsl:value-of select="#total_area"/>
</td>
<td>
<xsl:value-of select="#population"/>
</td>
<td>
<xsl:value-of select="#inflation"/>
</td>
</tr>
</xsl:template>
Did you note that the <xsl:if> "magically" disappeared?
Of course, this code can be improved even further, but you haven't provided neither an instance of the XML document on which the transformation is to be applied, nor the desired output.

How to delete an empty column in HTML table using XSLT?

How to delete an empty column in HTML table using XSLT, and having something like this:
<table id="cas6">
<tr>
<td />
<td>
<table>
<tr>
<td>rechin</td>
<td />
</tr>
<tr>
<td>amarillo</td>
<td />
</tr>
</table>
</td>
</tr>
</table>
<table id="cas7">
<tr>
<td>rechin</td>
<td />
</tr>
<tr>
<td>amarillo</td>
<td />
</tr>
<tr>
<td>this shouldn't been</td>
<td>deleted</td>
</tr>
</table>
To delete the empty column, this being said to remove td's which are empty in all tr's in a Xth position
Here is a very simple solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td[not(node())]">
<xsl:variable name="vPos" select="position()"/>
<xsl:if test="../../tr/td[position() = $vPos]/node()">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (made well-formed):
<html>
<table border="1" id="cas6">
<tr>
<td/>
<td>
<table border="1">
<tr>
<td>rechin</td>
<td />
</tr>
<tr>
<td>amarillo</td>
<td />
</tr>
</table></td>
</tr>
</table>
<table border="1" id="cas7">
<tr>
<td>rechin</td>
<td />
</tr>
<tr>
<td>amarillo</td>
<td />
</tr>
<tr>
<td>this shouldn't been</td>
<td>deleted</td>
</tr>
</table>
</html>
The wanted correct result is produced:
<html>
<table border="1" id="cas6">
<tr>
<td>
<table border="1">
<tr>
<td>rechin</td>
</tr>
<tr>
<td>amarillo</td>
</tr>
</table></td>
</tr>
</table>
<table border="1" id="cas7">
<tr>
<td>rechin</td>
<td></td>
</tr>
<tr>
<td>amarillo</td>
<td></td>
</tr>
<tr>
<td>this shouldn't been</td>
<td>deleted</td>
</tr>
</table>
</html>
This is the XSLT that worked for me.
<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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td[not(node())]">
<xsl:variable name="pos" select="position()" />
<xsl:variable name="emptyTds" select="count(../../tr/td[position() = $pos and not(node())])" />
<xsl:variable name="allTds" select="count(../../tr/td[position() = $pos])" />
<xsl:if test="$emptyTds != $allTds">
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
If my understanding of the question is correct, for a given table, if all entries in the nth column are empty, you want to delete that column from the table?
Try this XSLT then
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="td">
<xsl:variable name="columnNumber" select="position()"/>
<xsl:if test="../../tr/td[position()=$columnNumber][* or text()]">
<xsl:copy>
<xsl:value-of select="$columnNumber"/>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This is the 'identity' transform, but when it matches a TD element, it first gets the column number, and then checks if any other column in other rows are empty. If it finds any non-empty cells in the same column, it copies the TD element, otherwise it is ignored.

Matrix transposition in XSLT

I'm trying to go from this kind of input:
<col title="one">
<cell>a</cell> <cell>b</cell> <cell>c</cell> <cell>d</cell>
</col>
<col title="two">
<cell>e</cell> <cell>f</cell> <cell>g</cell>
</col>
... to this HTML output with XSLT:
<table>
<tr> <th>one</th> <th>two</th> </tr>
<tr> <td>a</td> <td>e</td> </tr>
<tr> <td>b</td> <td>f</td> </tr>
<tr> <td>c</td> <td>g</td> </tr>
<tr> <td>d</td> </tr>
</table>
In other words I want to perform a matrix transposition. I couldn't find a simple way to do that, there probably isn't, I guess; how about a complicated one? While searching on Google I found hints that a way to solve this was through recursion. Any idea appreciated.
One possibility is to find the <col> with the most cells and then iterate over them in a nested loop. This guarantees the generation of a structurally valid HTML table.
<!-- this variable stores the unique ID of the longest <col> -->
<xsl:variable name="vMaxColId">
<xsl:for-each select="/root/col">
<xsl:sort select="count(cell)" data-type="number" order="descending" />
<xsl:if test="position() = 1">
<xsl:value-of select="generate-id()" />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- and this selects the children of that <col> for later iteration -->
<xsl:variable name="vIter" select="
/root/col[generate-id() = $vMaxColId]/cell
" />
<xsl:template match="root">
<xsl:variable name="columns" select="col" />
<table>
<!-- output the <th>s -->
<tr>
<xsl:apply-templates select="$columns/#title" />
</tr>
<!-- make as many <tr>s as there are <cell>s in the longest <col> -->
<xsl:for-each select="$vIter">
<xsl:variable name="pos" select="position()" />
<tr>
<!-- make as many <td>s as there are <col>s -->
<xsl:for-each select="$columns">
<td>
<xsl:value-of select="cell[position() = $pos]" />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="col/#title">
<th>
<xsl:value-of select="." />
</th>
</xsl:template>
Applied to
<root>
<col title="one">
<cell>a</cell> <cell>b</cell> <cell>c</cell> <cell>d</cell>
</col>
<col title="two">
<cell>e</cell> <cell>f</cell> <cell>g</cell>
</col>
</root>
this produces:
<table>
<tr>
<th>one</th> <th>two</th>
</tr>
<tr>
<td>a</td> <td>e</td>
</tr>
<tr>
<td>b</td> <td>f</td>
</tr>
<tr>
<td>c</td> <td>g</td>
</tr>
<tr>
<td>d</td> <td></td>
</tr>
</table>
From Marrow:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="input">
<table border="1">
<xsl:apply-templates select="col[1]/cell"/>
</table>
</xsl:template>
<xsl:template match="cell">
<xsl:variable name="curr-pos" select="position()"/>
<tr>
<td>
<xsl:copy-of select="node()|../following-sibling::col/cell[$curr-pos]/node()"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
I put input tags around your xml to make it closer match an example I found.
(getting closer).
BTW: you can test by adding this as your 2nd line to your xml:
<?xml-stylesheet type="text/xsl" href="NonLinear.xslt"?>