XSLT foreach loop - xslt

I have only been introduced to xslt and xml in the last few weeks, and I urgently need some help to write some code to achieve what I want to achieve.
I have the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<abc1 formName="Form">
<Level1>
<Element1>ZZZ</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Pears</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Milkshakes</SubElement5>
</Element2>
</Level1>
<Level1>
<Element1>XXX</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Kiwifruit</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Soda</SubElement5>
</Element2>
</Level1>
</abc1>
and the following html table:
<table>
<tr>
<td width="180" > Row 1</td>
<td width="540" colspan="4"> Cell_A</td>
</tr>
<tr>
<td width="180" >Types</td>
<td width="180" >Type 1</td>
<td width="180" >Type 2</td>
<td width="180" >Type 3</td>
</tr>
<tr>
<td width="180" >Row 2</td>
<td width="180" >Cell_B</td>
<td width="180" >Cell_C</td>
<td width="180" >Cell_D</td>
</tr>
<tr>
<td width="180" > Row 3</td>
<td width="180" >Cell_E</td>
<td width="180" >Cell_F</td>
<td width="180" >Cell_G</td>
</tr>
<tr>
<td width="180" >Row 4</td>
<td width="180" >Cell_H</td>
<td width="180" >Cell_I</td>
<td width="180" >Cell_J</td>
</tr>
<tr>
<td width="180" > Row 5</td>
<td width="540" colspan="4"> Cell_K</td>
</tr>
</table>
I am having trouble using xslt to extract the data from the xml into the table because the rules that I need to apply make it very complicated. Below are the rules that need to apply to the cells that I am having troubles with.
(1) If the <Element1> value is the same throughout the xml, then:
If <Element1> = 'ZZZ' then Cell_B = '10', Cell_C = '20', and Cell_D = '30'
If <Element1> = 'XXX' then Cell_B = '100', Cell_C = '90', and Cell_D = '80'
but if the <Element1> value differs in the xml, then:
Cell_B = '10,100', Cell_C = '20,90', and Cell_D = '30,80'
(2) If the <SubElement5> value is the same throughout the xml, then:
Cell_J = the value of <SubElement5>
but if the <SubElement5> value differs in the xml, then:
Cell_J = the value of both <SubElement5> values separated by a comma
so in this case, the value for Cell_J would be 'Milkshakes, Soda'.
I have been experimenting with different things using:
<xsl:for-each select="./Level1/Element2">
<xsl:value-of select="./SubElement5"/>
</xsl:for-each>
but I cant determine what code I can use to check if they are the same because I can't overwrite the value of a variable.
Edit:
Please note that the cells I've indicated above (cells b,c,d, and j) are the only ones I
need help with. Additionally, for Element1 there are four potential values I can
encounter: ZZZ, XXX, AAA, and BBB. The required values for each of these would be:
(cells b,c, and d)
ZZZ:10,20,30
XXX:100,90,80
AAA:40,30,30
BBB:50,30,20
so if there was only one potential value in the whole xml, the cells should appear with the values as above. If the two element 1 values are different, then the cells should list the above values in each cell, separated by a comma.
With respect to cell_j, I'll try and explain it a little better.
First, I need to determine if <SubElement5> is the same value throughout the xml. In this case it is not, in one section it is Milkshakes and in the other it is 'Soda'. Therefore, cell_J should contain the text 'Milkshakes, Soda'.
If the xml looked like this:
<?xml version="1.0" encoding="UTF-8"?>
<abc1 formName="Form">
<Level1>
<Element1>ZZZ</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Pears</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Milkshakes</SubElement5>
</Element2>
</Level1>
<Level1>
<Element1>XXX</Element1>
<Element2>
<SubElement1>Apples</SubElement1>
<SubElement2>Oranges</SubElement2>
<SubElement3>Kiwifruit</SubElement3>
<SubElement4>Blueberries</SubElement4>
<SubElement5>Milkshakes</SubElement5>
</Element2>
</Level1>
</abc1>
Then the value for cell_j would just be 'Milkshakes'.
Thanks in advance to anyone who can help.
ANSWER TO THIS QUESTION:
To Summarize what Woody did below for anybody's future reference:
<?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="/abc1">
<xsl:variable name="elements" select="//Element1[not(preceding::Element1 = .)]"/>
<table>
<tr>
<td width="180" > Row 1</td>
<td width="540" colspan="4"> Cell_A</td>
</tr>
<tr>
<td width="180" >Types</td>
<td width="180" >Type 1</td>
<td width="180" >Type 2</td>
<td width="180" >Type 3</td>
</tr>
<tr>
<td width="180" >Row 2</td>
<td width="180" > <xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">40</xsl:when>
<xsl:when test=". = 'BBB'">50</xsl:when>
<xsl:when test=". = 'XXX'">100</xsl:when>
<xsl:when test=". = 'ZZZ'">10</xsl:when>
</xsl:choose>
</xsl:for-each></td>
<td width="180" ><xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">30</xsl:when>
<xsl:when test=". = 'XXX'">90</xsl:when>
<xsl:when test=". = 'ZZZ'">20</xsl:when>
</xsl:choose>
</xsl:for-each></td>
<td width="180" ><xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">20</xsl:when>
<xsl:when test=". = 'XXX'">80</xsl:when>
<xsl:when test=". = 'ZZZ'">30</xsl:when>
</xsl:choose>
</xsl:for-each></td>
</tr>
<tr>
<td width="180" > Row 3</td>
<td width="180" >Cell_E</td>
<td width="180" >Cell_F</td>
<td width="180" >Cell_G</td>
</tr>
<tr>
<td width="180" >Row 4</td>
<td width="180" >Cell_H</td>
<td width="180" >Cell_I</td>
<td width="180" ><xsl:for-each select="//SubElement5[not(preceding::SubElement5/text() = text())]">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each></td>
</tr>
<tr>
<td width="180" > Row 5</td>
<td width="540" colspan="4"> Cell_K</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
Woody, thank-you again. This was awesome.

THe key with your problem is that you can't itterate through the data to get what you want, as the answers you want are fixed, ie, if this, output that, if that, output this other. So you need to take each section at a time.
Are there only ever two Level1s?
<xsl:template match="/abc1">
<table>
<tr>
<td width="180" >Row 2</td>
<xsl:choose>
<xsl:when test="not(Level1/Element1='XXX')">
<!-- only ZZZ -->
<td width="180" >10</td>
<td width="180" >20</td>
<td width="180" >30</td>
</xsl:when>
<xsl:when test="not(Level1/Element1='ZZZ')">
<!-- only YYY -->
<td width="180" >100</td>
<td width="180" >90</td>
<td width="180" >80</td>
</xsl:when>
<xsl:otherwise>
<!-- some combination -->
<td width="180" >10,100</td>
<td width="180" >20,90</td>
<td width="180" >30,80</td>
</xsl:otherwise>
</xsl:choose>
</tr>
.. continued
</table>
</xsl:template>
</xsl:stylesheet>
and so on per section. If you have a number of rows, and you want them comma separated then you need to do it per section
or if you have to put one comma list for each section.
Sorry, I couldn't see what you were trying to do with the cell_j and there didn't seem to be a rule for the other ones
Edit: However, if you have a lot of items rather than just 2, and you need a comma separated list, you can do it with xpath, so:
<tr>
<td>
<xsl:for-each select="//SubElement5[not(preceding::SubElement5/text() = text())]">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</td>
.. maybe other rows the same
</tr>
Edit again:
So for your updated question about cell_j, the above comes up with the correct values for it.
For your update in the first section, it would be possible to do a variation on that same theme, if you want all the values shown (so there are 4 values if you have all of your 4 options). Unfortunately as you have fixed values for each, and you have to do each one by one, you would need to do it in a large section, so in a loop:
<xsl:for-each select="//Element1[not(preceding::Element1/text() = text())]">
which would put you in a loop for each unique entry and then have a choose element based on whether that value was XXX, ZZZ etc.
Edit again
There are several ways of doing the first section you want, including recursive functions, external documents and using the node-set function of various different XSLT implimentations, but as a totally generic easy to see way, this is the slightly wordy, but easy to see version (I hope):
<xsl:variable name="elements" select="//Element1[not(preceding::Element1 = .)]"/>
<table>
<tr>
<td width="180" >Row 2</td>
<td>
<xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">40</xsl:when>
<xsl:when test=". = 'BBB'">50</xsl:when>
<xsl:when test=". = 'XXX'">100</xsl:when>
<xsl:when test=". = 'ZZZ'">10</xsl:when>
</xsl:choose>
</xsl:for-each>
</td>
<td>
<xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">30</xsl:when>
<xsl:when test=". = 'XXX'">90</xsl:when>
<xsl:when test=". = 'ZZZ'">20</xsl:when>
</xsl:choose>
</xsl:for-each>
</td>
<td>
<xsl:for-each select="$elements">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:choose>
<xsl:when test=". = 'AAA'">30</xsl:when>
<xsl:when test=". = 'BBB'">20</xsl:when>
<xsl:when test=". = 'XXX'">80</xsl:when>
<xsl:when test=". = 'ZZZ'">30</xsl:when>
</xsl:choose>
</xsl:for-each>
</td>
</tr>
</table>
actually - I am not sure if the iterating on a variable ($elements in this case) is that standard, it may be a saxon and msxsl thing, but if it isn't, you can replace it with the value (//Element1[not(preceding::Element1 = .)])

Related

XSLT: Count the iteration of a cases inside a loop

I have two loops and want to count the iteration of cases OK and NotOK and overall cases inside xslt file.
How I cold do so? If I want to know the sum of both iteration how could I write it?
my For loops are as:
<xsl:for-each select="test">
<xsl:variable name="LinkName" select="attribute::name"/>
<tr>
<th style="text-align:left;vertical-align:top;position:"><a name="{$LinkName}"><xsl:value-of select="$LinkName"/></a></th>
<xsl:for-each select="descendant::node()">
<xsl:choose>
<xsl:when test="attribute::state='NotOK'">
<tr>
<td bgcolor="red"><xsl:value-of select="description"/></td>
</tr>
</xsl:when>
<xsl:when test="attribute::state='OK'">
<tr>
<td bgcolor="lime"><xsl:value-of select="description"/></td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</tr>
</xsl:for-each>
Update:
<table>
<tr bgcolor="coral">
<th>Test cases</th>
<th>Info</th>
</tr>
<xsl:for-each select="test">
<xsl:variable name="Summation"/>
<xsl:variable name="LinkIt" select="#name"/>
<xsl:choose>
<xsl:when test="descendant::node()/#state='NotOK'">
<tr>
<td bgcolor="red"><xsl:value-of select="$LinkIt"/></td>
<td>
<xsl:value-of select="count(descendant::node()[#state='NotOK'])"/> of <xsl:value-of select="count(descendant::node()[#state='OK']) + count(descendant::node()[#state='NotOK'])"/>
</td>
</tr>
</xsl:when>
<xsl:when test="descendant::node()/attribute::state='OK'">
<tr>
<td bgcolor="lime"><xsl:value-of select="$LinkIt"/></td>
<td>
---
</td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</table>
for-each is not a loop. If you want to count all descendant nodes of the test elements then simply use <xsl:value-of select="count(test/descendant::node())"/>. You can also add predicates to XPath expressions, like count(test/descendant::node()[attribute::state='NotOK']).
Inside of a <xsl:for-each select="test"> the context node is a test element so any count expression should be relative to that e.g. count(descendant::node()[attribute::state='NotOK']).

XSLT: foreach with a nested IF clause, how to get "first TRUE result"?

I have a problem in the following XSLT Code.
Foreach loop generates 14 records, and 4 of them are selected by
<xsl:if test="esperto = 'F'">
clause.
<xsl:for-each select="../partite/risultatopartita">
<xsl:if test="esperto = 'F'">
<tr>
<xsl:if test="position()=1">
<td rowspan="4" class='centra'>Favorite</td>
</xsl:if>
<td class='centra'>
<xsl:value-of select="posizione"/>
</td>
<td class='centra'>
<xsl:value-of select="squadra1"/>
</td>
<td class='centra'>
<xsl:value-of select="squadra2"/>
</td>
</tr>
</xsl:if>
</xsl:for-each>
I need that first TD (one with rowspan="4") will be printed only first time, BUT "first time that if clause is true, not first foreach iteration".
Infact, <xsl:if test="position()=1"> get first foreach itearation but is obviously possible that iteration that satisfies <xsl:if test="esperto = 'F'"> could not be the first one of the foreach.
How to solve it?
Desired HMTL code is:
<table>
<tr>
<td rowspan="4" class='centra'>Favorite</td>
<td class="centra">2</td>
<td class="centra">fooText 1</td>
<td class="centra">foofooText 1</td>
</tr>
<tr>
<td class="centra">6</td>
<td class="centra">fooText 2</td>
<td class="centra">foofooText 2</td>
</tr>
<tr>
<td class="centra">10</td>
<td class="centra">fooText 3</td>
<td class="centra">foofooText 3</td>
</tr>
<tr>
<td class="centra">14</td>
<td class="centra">fooText 4</td>
<td class="centra">foofooText 4</td>
</tr>
</table>
Instead of
<xsl:for-each select="../partite/risultatopartita">
<xsl:if test="esperto = 'F'">
you should simply put a predicate in the select expression, i.e. use
<xsl:for-each select="../partite/risultatopartita[esperto = 'F']">
That should do, at least as long your real code also has nothing but the xsl:if inside of the xsl:for-each.
It is quite tough to find an answer without an input XML.
You can use the following code instead of your for-each block:
<xsl:for-each select="../partite/risultatopartita">
<xsl:if test="esperto = 'F'">
<tr>
<xsl:if test="not(preceding-sibling::risultatopartita/esperto='F')">
<td rowspan="4" class='centra'>Favorite</td>
</xsl:if>
<td class='centra'>
<xsl:value-of select="posizione"/>
</td>
<td class='centra'>
<xsl:value-of select="squadra1"/>
</td>
<td class='centra'>
<xsl:value-of select="squadra2"/>
</td>
</tr>
</xsl:if>
</xsl:for-each>

Move a string to a header element from child only if the string exists in all the child elements using XSLT

My XML code & XSLT code
explanation :
I'm trying to move the string '2' from the elements with
(tr[#type='detail'] and td[#column='1'])
to the category header
(tr [#type='categoryhead' and level='2'])
Any help on this is greatly appreciated
Thanks a ton
<!--=============My XML=============-->
<tbody xmlns="http://mynamespace.com">
<tr layoutcode="" type="categoryhead" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">Bonds</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41164">
<td colname="1">Security_1(1,2)</td>
<td colname="2">500</td>`enter code here`
<td colname="3">330</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41167">
<td colname="1">Security_4(1,2,3,4)</td>
<td colname="2">10</td>
<td colname="3">265</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categorytotal" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">TOTAL Bonds</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categoryhead" level="1" categorykey="2936" hierarchykey="4921">
<td colname="1">Options</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,1)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,2)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
</tbody>
XSLT where I'm trying to move the string '2' from the elements with (tr[#type='detail'] and td[#column='1'])to the category header (tr [#type='categoryhead' and level='2'])
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://mynamespace.com" version="2.0">
<!-- Global Variable -->
<xsl:variable name="arg1" select="'2'"></xsl:variable>
<!-- This identity template copies the document -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #* "/>
</xsl:copy>
</xsl:template>
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td">
<xsl:for-each select="//a:tbody/a:tr[#type='detail']/a:td[#colname='1'][contains(.,$arg1)]">
<xsl:variable name="IsFooted" select="contains(.,$arg1)"></xsl:variable>
<xsl:value-of select="count(//a:tbody/a:tr[#type='detail']/a:td[#colname='1'][contains(.,$arg1)])"/>
<xsl:choose>
<xsl:when test="$IsFooted='true'">
<xsl:value-of select="."/>
<xsl:value-of select="concat('(',concat($arg1,')'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Desired XML Output:
<tbody xmlns="http://mynamespace.com">
<tr layoutcode="" type="categoryhead" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">Bonds</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages (2)</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41164">
<td colname="1">Security_1(1)</td>
<td colname="2">500</td>
<td colname="3">330</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41167">
<td colname="1">Security_4(1,3,4)</td>
<td colname="2">10</td>
<td colname="3">265</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categorytotal" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">TOTAL Bonds</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categoryhead" level="1" categorykey="2936" hierarchykey="4921">
<td colname="1">Options</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,1)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,2)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
</tbody>
The terminology is not quite clear here, as looking at the output sample, all that is happening is a "(2)" is being appended to a particular table cell, so it is not really moving anything.
Also, your question title of the question mentions about child elements, but looking at the XML structure, the "detail" rows are actually siblings of the "categoryhead" rows. This is probably where the problem lies; how do you correlate the "detail" rows with the associated "categoryheader". One way to do this could be using a key
<xsl:key name="row"
match="a:tr[#level != '1']"
use="generate-id(preceding-sibling::a:tr[#level=current()/#level - 1][1])" />
This gets the rows (other than at level 1) and groups them by the first preceding row with a lowel #level attribute.
Now, for your template match, because you are changing "td" elements, I would change the template to match such an element, rather than the "tr" element
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td[#colname='1']">
You can then use the key to get the 'child' elements, but instead of thinking in terms of if all child elements contain a '2', reverse the logic and check whether any child eleent doesn't contain a '2'
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://composition.bowne.com/2010/v4" version="1.0">
<!-- Global Variable -->
<xsl:variable name="arg1" select="'2'"></xsl:variable>
<xsl:key name="row" match="a:tr[#level != '1']" use="generate-id(preceding-sibling::a:tr[#level=current()/#level - 1][1])" />
<!-- This identity template copies the document -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td[#colname='1']">
<xsl:variable name="IsMissing" select="key('row', generate-id(..))/a:td[#colname='1'][not(contains(text(), $arg1))]" />
<xsl:choose>
<xsl:when test="not($IsMissing)">
<xsl:value-of select="."/>
<xsl:value-of select="concat('(',concat($arg1,')'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As an aside, the namespace in your XSLT does not match the namespace in your XML. You would need these to match for this to work, but I assume this is a typo.
EDIT: To remove the '2' from the 'detail' rows, try adding the following template. The 'isMissing' variable is a bit messier, because it has to find the associated 'categoryhead' first. Also note it uses 'replace' which is only available in XSLT 2.0.
<xsl:template match="a:tbody/a:tr[#type='detail']/a:td[#colname='1']">
<xsl:variable name="parentLevel" select="../#level - 1" />
<xsl:variable name="IsMissing" select="key('row', generate-id(../preceding-sibling::a:tr[#level=$parentLevel][1]))/a:td[#colname='1'][not(contains(text(), $arg1))]" />
<xsl:choose>
<xsl:when test="not($IsMissing)">
<xsl:value-of select="replace(., concat(',', $arg1), '')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Alternate Row Color of Table using XSLT

I have an XSLT where I want to alternate the row colors of an output table. I know that you can use the code such as in this example:
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<xsl:for-each select="//Book">
<xsl:variable name="altColor">
<xsl:choose>
<xsl:when test="position() mod 2 = 0">#FFFFFF</xsl:when>
<xsl:otherwise>#D3DFEE</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr bgcolor="{$altColor}">
<td><xsl:value-of select="current()/#name"/></td>
<td><xsl:value-of select="current()/#ID"/></td>
</tr>
</xsl:for-each>
</table>
which works fine however, I have a few instances where I need to include some if statements within the for-each, such as.
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<xsl:for-each select="//Book">
<xsl:variable name="altColor">
<xsl:choose>
<xsl:when test="position() mod 2 = 0">#FFFFFF</xsl:when>
<xsl:otherwise>#D3DFEE</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="current()/#ID > 10000 and current/#ID < 6000">
<tr bgcolor="{$altColor}">
<td><xsl:value-of select="current()/#name"/></td>
<td><xsl:value-of select="current()/#ID"/></td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
Then it doesn't work because it may skip an item at a position in the for-each and I end up with randomly alternating row colors or it may start at an incorrect position where the rows alternate starting with the incorrect color.
I've tried adding an xsl:sort, which doesn't really fix the problem. Is there a way to avoid this snag?
Try with the following code:
tr[position() mod 2 =0]
<xsl:template match="n1:tr[position() mod 2 =0]">
<tr bgcolor="#aaaaaa">
<xsl:apply-templates/>
</tr>
</xsl:template>
<xsl:template match="n1:tr[position() mod 2 =1]">
<tr bgcolor="#aaaaff">
<xsl:apply-templates/>
</tr>
</xsl:template>
The simplest solution (taking your sample stylesheet as representative of your actual needs) is to only loop over the desired nodes in the first place. Like this:
<xsl:template match="/">
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<xsl:for-each select="//Book[#ID < 10000 and #ID > 6000]">
<xsl:variable name="altColor">
<xsl:choose>
<xsl:when test="position() mod 2 = 0">#FFFFFF</xsl:when>
<xsl:otherwise>#D3DFEE</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr bgcolor="{$altColor}">
<td><xsl:value-of select="#name" /></td>
<td><xsl:value-of select="#ID" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
In other words, instead of conditionally including nodes in the body of the for-each simply select only those nodes you need. By doing this, position() will be relative to the set that you're iterating and it will work as expected.
For example, this (simplified) input:
<r>
<Book ID="6200"/>
<Book ID="7100"/>
<Book/>
<Book/>
<Book ID="8000"/>
<Book/>
<Book ID="9001"/>
<Book ID="9002"/>
</r>
Produces the correct alternation:
<table>
<tr>
<td>Name</td>
<td>ID</td>
</tr>
<tr bgcolor="#D3DFEE">
<td />
<td>6200</td>
</tr>
<tr bgcolor="#FFFFFF">
<td />
<td>7100</td>
</tr>
<tr bgcolor="#D3DFEE">
<td />
<td>8000</td>
</tr>
<tr bgcolor="#FFFFFF">
<td />
<td>9001</td>
</tr>
<tr bgcolor="#D3DFEE">
<td />
<td>9002</td>
</tr>
</table>
one simple solution would be to remember last color used and than use opposing one
instead basing your choice on position() and also you should move choice inside test for row
here is pseudo code
currentColor = color1
for each
if ( id > 10000 and id < 6000 ) {
if ( currentColor == color1 )
currentColor= color2
else
currentColor = color1
showDataInColor(currentColor)
}

XML to table, how to get position() of context node to the specified ancestor(s)

I have such xml:
<A1>
<B1>
<C1>
<C2>
<C3>
</B1>
<B2>
<C4>
<C5>
<C6>
</B2>
</A1>
<A2>
<B3>
<C7>
<C8>
<C9>
</B3>
<B4>
<C10>
<C11>
<C12>
</B4>
</A2>
I need to transform it to table with nested rows:
<table border="yes">
<tr>
<td>A1</td>
<td>B1</td>
<td>C1</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C2</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C3</td>
</tr>
<tr>
<td></td>
<td>B2</td>
<td>C3</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C4</td>
</tr>
A and B appear only if they are new (not repeating in every row);
I'm trying to use position()
<xsl:template match="c">
<tr>
<td>
<xsl:if test="IT IS THE FIRST C IN A">
<xsl:value-of select="ancestor::A"/>
</xsl:if>
</td>
<td>
<xsl:if test="position(1)">
<xsl:value-of select="parent"/>
</xsl:if>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
It seems that we should emulate position() for ancestor.
Is there a general solution for any number of nested rows?
You perhaps need something like the following (if I understood your question correctly):
<xsl:template match="C">
<tr>
<td>
<xsl:if test="generate-id(ancestor::A/descendant::C[1]) = generate-id(.)">
<xsl:value-of select="ancestor::A"/>
</xsl:if>
</td>
<td>
<xsl:if test="not(previous-sibling::C)">
<xsl:value-of select=".."/>
</xsl:if>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="descendant::C"/>
</xsl:template>
Edit: you may also use
not(previous-sibling::C) and not(../previous-sibling::B)
as the first test (instead of using generate-id()).