how to do zebra stripe in xslt - xslt

I'm trying do zebra stripe for my pdf file. The XML like as below:
<root>
<order>
<attribute1>1</attribute1>
<attribute2>2</attribute2>
<attribute3>0</attribute3>
<attribute4>4</attribute4>
<attribute5/>
</order>
</root>
The attribute3 isn't appeared if the value is '0'. Also attribute5 isn't appear if there is no value for it. So I cannot do zebra stripe like as below:
<fo:table-row (colored)>
<fo:table-cell>
<fo:block>
<xsl:text>Attribute1</xsl:text>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:text>...</xsl:text>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row (non colored)>
<fo:table-cell>
<fo:block>
<xsl:text>Attribute2</xsl:text>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:text>...</xsl:text>
</fo:block>
</fo:table-cell>
</fo:table-row>
Because attribute3 and attribute5 is not always appeared in pdf file. How can I do it?

What you need to do here, is first use xsl:apply-templates to select only the child nodes you wish to output (This assumes you are currently positioned on the order element:
<xsl:apply-templates select="*[normalize-space()][. != '0']" />
Then you have a template to match child elements of order elements, like so:
Within this template, you can then output the table-row, and to do the 'coloured' attribute, you can test the 'position' of the current attribute, to see if it is odd or even:
<fo:table-row>
<xsl:if test="position() mod 2 = 0">
<xsl:attribute name="colour">zebra</xsl:attribute>
</xsl:if>
Try this XSLT as a start
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="order">
<fo:table>
<xsl:apply-templates select="*[normalize-space()][. != '0']" />
</fo:table>
</xsl:template>
<xsl:template match="order/*">
<fo:table-row>
<xsl:if test="position() mod 2 = 0">
<xsl:attribute name="colour">zebra</xsl:attribute>
</xsl:if>
<fo:table-cell>
<fo:block>
<xsl:value-of select="local-name()" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:text>...</xsl:text>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
</xsl:stylesheet>
Obviously you would use the correct xsl-fo styling here, and not literally the 'colour=zebra' attribute as shown here....

Related

remove table cell in xsl-fo

I have this problem where I need to remove table cells when they are empty. But they always leave a small gap.
If the user fills out every option, the table is normal. If the user only fills out some fields then the table generates this dark horizontal line.
I suspect it is the empty cells bunching up.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" media-type="text/html" />
<xsl:template match="fwcF17Part35Page">
<xsl:apply-templates select="part35Details"/>
</xsl:template>
<xsl:template match="part35Details">
<fo:block keep-together.within-page="always">
<fo:block padding="5pt"/>
<fo:block font-size="16pt" text-align="left">
Upload Documents - test
</fo:block>
<fo:block>
<fo:leader leader-pattern="rule" leader-length="100%" rule-style="solid" rule-thickness="2pt"/>
</fo:block>
<fo:block padding="8pt"/>
<fo:block background-color="#ebe8e8" font-size="10pt" font-weight="bold">
Document(s)
</fo:block>
<fo:block padding="4px" font-size="8pt" font-weight="normal" text-align="center">
<fo:table>
<fo:table-column column-width="50%" />
<fo:table-column column-width="50%" />
<fo:table-header>
<fo:table-row>
<fo:table-cell border-style="solid" padding="2pt">
<fo:block font-weight="bold">Document Name</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" padding="2pt">
<fo:block font-weight="bold">Document Type</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:for-each select="/root/pages/page_12/sections/fwcF17DocumentUpload/data">
<fo:table-row>
<xsl:choose>
<xsl:when test="size > 0">
<fo:table-cell border-style="solid" padding="2pt">
<fo:block>
<xsl:value-of select="name" />
</fo:block>
</fo:table-cell>
</xsl:when>
<xsl:otherwise>
<fo:table-cell>
<fo:block>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="size > 0">
<fo:table-cell border-style="solid" padding="2pt">
<fo:block>
<xsl:value-of select="documentType" />
</fo:block>
</fo:table-cell>
</xsl:when>
<xsl:otherwise>
<fo:table-cell>
<fo:block>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
</xsl:choose>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
</fo:block>
</xsl:template>
</xsl:stylesheet>
I have tried putting in:
<fo:table-cell visilibity="hidden">
<fo:table-cell display="none">
<fo:table-cell height="0px">
Wrong output when there are empty cells:
Correct output when there are no empty cells:
Only create fo:table-row if your data fulfills the filter size > 0
You can do that by adding a predicate to your for-each.
In addition you can use that same predicate in the match, te ensure that there will be not a invalid fo:table constructed.
The xslt would then look like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" media-type="text/html" />
<xsl:template match="fwcF17Part35Page">
<xsl:apply-templates select="part35Details"/>
</xsl:template>
<xsl:template match="part35Details[/root/pages/page_12/sections/fwcF17DocumentUpload/data[size > 0]]">
<fo:block keep-together.within-page="always">
<fo:block padding="5pt"/>
<fo:block font-size="16pt" text-align="left">
Upload Documents - test
</fo:block>
<fo:block>
<fo:leader leader-pattern="rule" leader-length="100%" rule-style="solid" rule-thickness="2pt"/>
</fo:block>
<fo:block padding="8pt"/>
<fo:block background-color="#ebe8e8" font-size="10pt" font-weight="bold">
Document(s)
</fo:block>
<fo:block padding="4px" font-size="8pt" font-weight="normal" text-align="center">
<fo:table>
<fo:table-column column-width="50%" />
<fo:table-column column-width="50%" />
<fo:table-header>
<fo:table-row>
<fo:table-cell border-style="solid" padding="2pt">
<fo:block font-weight="bold">Document Name</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" padding="2pt">
<fo:block font-weight="bold">Document Type</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:for-each select="/root/pages/page_12/sections/fwcF17DocumentUpload/data[size > 0]">
<fo:table-row>
<fo:table-cell border-style="solid" padding="2pt">
<fo:block>
<xsl:value-of select="name" />
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" padding="2pt">
<fo:block>
<xsl:value-of select="documentType" />
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
</fo:block>
</xsl:template>
</xsl:stylesheet>
It would be really helpful if you would add a minimal xml-source-sample to your question to better understand your xslt.
I.e: your template match: <xsl:template match="part35Details"> looks somewhat strange. The template does not use the context.

Using a param as the argument to XSL select statement

I am using apache fop to produce a PDF, I have an XSL-FO template that produces a table and I would like to be able to call the template multiple times, with different select parameters.
here is my xsl stylesheet
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:fox="http://xmlgraphics.apache.org/fop/extensions"
version="1.0">
<xsl:output method="html"/>
<xsl:template name="checklist">
<xsl:param name="H1"/>
<xsl:param name="H2"/>
<xsl:param name="H3"/>
<xsl:param name="src"/>
<fo:block width="19cm" >
<fo:table font-size="8pt" table-layout="fixed" width="100%">
<fo:table-column column-width="1.2cm"/>
<fo:table-column column-width="16.5cm"/>
<fo:table-column column-width="1.2cm"/>
<fo:table-header font-weight="bold" background-color="lightgrey">
<fo:table-row>
<fo:table-cell>
<fo:block><xsl:value-of select="$H1"/></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block text-align="center"><xsl:value-of select="$H2"/></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block><xsl:value-of select="$H3"/></fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:apply-templates select="$src" />
</fo:table-body>
</fo:table>
</fo:block>
</xsl:template>
<xsl:template match="item">
<fo:table-row line-stacking-strategy="line-height" margin="0mm" space-before="1mm" background-color="white">
<xsl:variable name="hdr" select="hdr"/>
<xsl:choose>
<xsl:when test="$hdr = 'y'">
<fo:table-cell number-columns-spanned="3" background-color="lightgrey" border="1px solid #b8b6b6">
<fo:block>
<fo:inline>
<xsl:value-of select="id"/> 
<xsl:value-of select="description"/>
</fo:inline>
</fo:block>
</fo:table-cell>
</xsl:when>
<xsl:otherwise>
<fo:table-cell border="1px solid #b8b6b6" vertical-align="middle" text-align="center">
<fo:block line-height="4mm">
<xsl:value-of select="id"/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="1px solid #b8b6b6" padding-left="3pt">
<fo:block>
<xsl:value-of select="description"/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="1px solid #b8b6b6" text-align="center" padding-right="3pt">
<xsl:variable name="outcome" select="outcome"/>
<fo:block color="white">
<!--<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/c1.svg" width="4.0mm" height="4.0mm" content-width="scale-to-fit" content-height="scale-to-fit"/>-->
<xsl:choose>
<xsl:when test="$outcome = 'ok'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/c1n.svg" vertical-align="middle" height="4.0mm" content-height="scale-to-fit"/>
</xsl:when>
<xsl:when test="$outcome = 'c1'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/ok.svg" vertical-align="middle" height="3.9mm" content-height="scale-to-fit"/>
</xsl:when>
<xsl:when test="$outcome = 'c2'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/c2.svg" vertical-align="middle" height="3.8mm" content-height="scale-to-fit"/>
</xsl:when>
<xsl:when test="$outcome = 'c3'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/c3.svg" vertical-align="middle" height="3.7mm" content-height="scale-to-fit"/>
</xsl:when>
<xsl:when test="$outcome = 'fi'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/fi.svg" vertical-align="middle" height="3.6mm" content-height="scale-to-fit"/>
</xsl:when>
<xsl:when test="$outcome = 'nv'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/nv.svg" vertical-align="middle" height="3.5mm" content-height="scale-to-fit"/>
</xsl:when>
<xsl:when test="$outcome = 'na'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/na.svg" vertical-align="middle" height="3.4mm" content-height="scale-to-fit"/>
</xsl:when>
<xsl:when test="$outcome = 'lim'">
<fo:external-graphic src="file:///F:/Projects/Active/eCert/src/resources/lim.svg" vertical-align="middle" height="3.3mm" content-height="scale-to-fit"/>
</xsl:when>
</xsl:choose>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
</xsl:choose>
</fo:table-row>
</xsl:template>
</xsl:stylesheet>
And here is one of the flows that calls the template
<fo:flow flow-name="xsl-region-body">
<fo:block />
<xsl:call-template name="checklist">
<xsl:with-param name="H1" select="'Item No.'"/>
<xsl:with-param name="H2" select="'Description'"/>
<xsl:with-param name="H3" select="'Outcome'"/>
<xsl:with-param name="src" select="'eicr/checklist/item'"/>
</xsl:call-template>
</fo:flow>
When I execute FOP I get the following error ** Can not convert #STRING to a NodeList!**
Parameters H1, H2 and H3 are used as the column headers and that part works fine
The src param is the one that should be used to select the items from from an xml list.
I am new xsl fo so any help or pointers to documentation that can help me achieve the desired result would be appreciated.
Many thanks
Remove the single-quotes around eicr/checklist/item. 'eicr/checklist/item' is a string literal; eicr/checklist/item is a location path that will select elements relative to the context node.
The alternative that I was trying to avoid (because it's more verbose) is to apply templates before or as part of calling the template and then just copying the result to the result tree:
<xsl:with-param name="src">
<xsl:apply-templates select="eicr/checklist/item" />
</xsl:with-param>
<fo:table-body>
<xsl:copy-of select="$src" />
</fo:table-body>
It's a technique that you see in the DocBook XSLT 1.0 stylesheets, e.g.:
https://github.com/docbook/xslt10-stylesheets/blob/6ff683948c5a85949e4b7661f302e8b5f12f7bf2/xsl/fo/block.xsl#L181

xsl:choose find the first position of elements but not the second

I would like to code an XSLT to convert an XML document to an XML-FO document, in order to generate a PDF.
This Image shows the current Output
So my Table has 3 columns. In the first 4 lines the second and third columns are merged.
My XML document looks like this:
<WS-Standards>
<tags>
<tag category = "parameters" > <!-- WS_Beer_Type -->
<tag_name>WS_Beer_TypeX</tag_name> <!-- browsename -->
<tag_number>30004</tag_number>
<datatype>Unsigned32</datatype>
<accessrights>RW</accessrights>
<names>
<name language="DE">Biersorte</name>
<name language="EN">Beer Type</name>
</names>
</tag>
</tags>
</WS-Standards>
My current XSLT document:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" indent="yes"/>
<xsl:template match="WS-Standards">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master page-height="297mm" page-width="210mm"
margin="5mm 25mm 5mm 25mm" master-name="PageMaster">
<fo:region-body margin="20mm 0mm 20mm 0mm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="PageMaster">
<fo:flow flow-name="xsl-region-body">
<fo:block>
<xsl:apply-templates select="tags"/>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="tags">
<fo:block>
<xsl:apply-templates select="tag"/>
</fo:block>
</xsl:template>
<xsl:template match="tag">
<fo:block space-before="6pt" border-top="3pt solid green">
<fo:table>
<fo:table-column column-number="1" column-width="20%" border-style="solid"
border-width="1pt"/>
<fo:table-column column-number="2" column-width="10%" border-style="solid"
border-width="1pt"/>
<fo:table-column column-number="3" column-width="70%" border-style="solid"
border-width="1pt"/>
<fo:table-body>
<xsl:apply-templates/>
</fo:table-body>
</fo:table>
</fo:block>
</xsl:template>
<xsl:template match="tag_name">
<fo:table-row>
<fo:table-cell column-number="1" border="1pt solid black">
<fo:block> Tag Name </fo:block>
</fo:table-cell>
<fo:table-cell column-number="2" number-columns-spanned="2" border="1pt solid black">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
<xsl:template match="tag_number">
<fo:table-row>
<fo:table-cell column-number="1" border="1pt solid black">
<fo:block> Tag-Nummer </fo:block>
</fo:table-cell>
<fo:table-cell column-number="2" number-columns-spanned="2" border="1pt solid black">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
<xsl:template match="datatype">
<fo:table-row>
<fo:table-cell column-number="1" border="1pt solid black">
<fo:block> Datentyp </fo:block>
</fo:table-cell>
<fo:table-cell column-number="2" number-columns-spanned="2" border="1pt solid black">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
<xsl:template match="accessrights">
<fo:table-row>
<fo:table-cell column-number="1" border="1pt solid black">
<fo:block> Zugriffsrechte </fo:block>
</fo:table-cell>
<fo:table-cell column-number="2" number-columns-spanned="2" border="1pt solid black">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
<xsl:template match="names">
<xsl:choose>
<xsl:when test="number(./name/position()) = 1">
<fo:table-row>
<fo:table-cell column-number="1" border="1pt solid black">
<fo:block> Name </fo:block>
</fo:table-cell>
<fo:table-cell column-number="2" border="1pt solid black">
<fo:block>
<xsl:value-of select="./name/#language"/>
</fo:block>
</fo:table-cell>
<fo:table-cell column-number="3" border="1pt solid black">
<fo:block>
<xsl:value-of select="./name"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:when>
<xsl:otherwise>
<fo:table-row>
<fo:table-cell column-number="1" border="1pt solid black">
<fo:block> NameXXX </fo:block>
</fo:table-cell>
<fo:table-cell column-number="2" border="1pt solid black">
<fo:block>
<xsl:value-of select="./name/#language"/>
</fo:block>
</fo:table-cell>
<fo:table-cell column-number="3" border="1pt solid black">
<fo:block>
<xsl:value-of select="./name"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="p">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="b">
<fo:inline font-weight="bold">
<xsl:apply-templates/>
</fo:inline>
</xsl:template>
</xsl:stylesheet>
My question is about the names element for different languages.
So the first Line in the table of the names element looks like this:
-----------------------------
|Name | DE | Biersorte |
-----------------------------
But I`m missing the second line:
-----------------------------
| | EN | beer type |
-----------------------------
What am I doing wrong with the xsl:choose->when->otherwise function?
I would be happy about a tip.
The relevant code is in a template matching names, but there is only one such element in your XSLT and so it will only be called once.
The expression number(./name/position()) = 1 in this context is simply asking "For this names element, is there a name element in position 1", which is true, and so the xsl:when gets executed.
Your code really needs to be in a block where name is selected. Try this XSLT (which also removes the code duplication).
<xsl:template match="names">
<xsl:for-each select="name">
<fo:table-row>
<fo:table-cell column-number="1" border="1pt solid black">
<fo:block>
<xsl:if test="position() = 1">Name</xsl:if>
</fo:block>
</fo:table-cell>
<fo:table-cell column-number="2" border="1pt solid black">
<fo:block>
<xsl:value-of select="#language"/>
</fo:block>
</fo:table-cell>
<fo:table-cell column-number="3" border="1pt solid black">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</xsl:template>

show list as table in xsl-fo

I'm trying to render this list as a table in xsl-fo
<ul class="tablelist">
<li class="a">A</li>
<li class="a">A
</li>
<li class="b">B
</li>
<li class="b">B
</li>
<li class="a">A</li>
<li class="b">B</li>
<li class="a">A</li>
</ul>
All the ones with class A in the left column, all the ones with class B in the right column.
My current solution:
<fo:table>
<fo:table-column column-number="1" column-width="30mm"/>
<fo:table-column column-number="2" />
<fo:table-body>
<xsl:for-each select="li[#class='a']">
<fo:table-row>
<fo:table-cell column-number="1">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
<xsl:for-each select="li[#class='b']">
<fo:table-row>
<fo:table-cell column-number="2">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
... doesnt work, of course. What would I need to change?
Thanks for help!
EDIT:
Desired output in this case would be:
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block>
A
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
B
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<fo:block>
A
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
B
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<fo:block>
A
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
B
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<fo:block>
A
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block/
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
because there are four class="a" and three class="b"elements. So, 4 rows in total, in 4 rows the left cell is A and in three of those rows, the right column is B.
Hope, it is a bit clearer now!
Here's another way you could look at it:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<table>
<xsl:call-template name="generate-rows">
<xsl:with-param name="left" select="ul/li[#class='a']"/>
<xsl:with-param name="right" select="ul/li[#class='b']"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="generate-rows">
<xsl:param name="left"/>
<xsl:param name="right"/>
<xsl:param name="i" select="1"/>
<xsl:if test="$i <= count($left) or $i <= count($right)">
<row>
<cell><xsl:value-of select="$left[$i]"/></cell>
<cell><xsl:value-of select="$right[$i]"/></cell>
</row>
<xsl:call-template name="generate-rows">
<xsl:with-param name="left" select="$left"/>
<xsl:with-param name="right" select="$right"/>
<xsl:with-param name="i" select="$i + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
One option is to use position() to apply-templates to the element with the opposite class that's in the same position.
If you know that there will always be more a class elements than b class elements (or if you don't care what happens to the remaining b class elements), you could do something like this:
<xsl:template match="ul[#class='tablelist']">
<fo:table>
<fo:table-column column-number="1" column-width="30mm"/>
<fo:table-column column-number="2" />
<fo:table-body>
<xsl:for-each select="li[#class='a']">
<xsl:variable name="pos" select="position()"/>
<fo:table-row>
<fo:table-cell column-number="1">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
<fo:table-cell column-number="2">
<fo:block>
<xsl:apply-templates select="../li[#class='b'][position()=$pos]"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</xsl:template>
If you do care about those b class elements, when there are more b than a, you could do something like this:
<xsl:template match="ul[#class='tablelist']">
<fo:table>
<fo:table-column column-number="1" column-width="30mm"/>
<fo:table-column column-number="2" />
<fo:table-body>
<xsl:choose>
<xsl:when test="count(li[#class='a']) >= count(li[#class='a'])">
<xsl:apply-templates select="li[#class='a']" mode="createRow"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="li[#class='b']" mode="createRow"/>
</xsl:otherwise>
</xsl:choose>
</fo:table-body>
</fo:table>
</xsl:template>
<xsl:template match="li[#class='a']" mode="createRow">
<xsl:variable name="pos" select="position()"/>
<fo:table-row>
<fo:table-cell column-number="1">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
<fo:table-cell column-number="2">
<fo:block>
<xsl:apply-templates select="../li[#class='b'][position()=$pos]"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
<xsl:template match="li[#class='b']" mode="createRow">
<xsl:variable name="pos" select="position()"/>
<fo:table-row>
<fo:table-cell column-number="1">
<fo:block>
<xsl:apply-templates select="../li[#class='a'][position()=$pos]"/>
</fo:block>
</fo:table-cell>
<fo:table-cell column-number="2">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>

why can't I navigate using the child element in this xpath expression?

In XSL 2.0, I'm trying to iterate through some data by the distinct values, and then do something with them.
<xsl:for-each select="distinct-values(InvoiceLine/Service/ServiceMnemonicCode)">
<xsl:variable name="mnemonic">
<xsl:value-of select="."/>
</xsl:variable>
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:value-of select="InvoiceLine/Service[ServiceMnemonic=$mnemonic]/ServiceDescription"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
However I end up with the following error:
XPTY0020: Axis step
child::element({http://schemas.blabla.com/etp/invoice/types}InvoiceLine, xs:anyType)
cannot be used here: the context item is an atomic value
ailed to compile stylesheet. 1 error detected.
I've bee googling furiously, and I do see people complaining about "atomic values" but I haven't seen anyone suggest what to do about it. I've using Saxon9. Any insight would be greatly appreciated.
Don't know if its the best solution, but this seems to work:
<xsl:template match="Invoice">
<xsl:variable name="invoice">
<xsl:copy-of select="."/>
</xsl:variable>
<fo:table border="0.5pt solid black" text-align="center">
<fo:table-body>
<xsl:for-each select="distinct-values(InvoiceLine/Service/ServiceMnemonicCode)">
<xsl:sort/>
<xsl:variable name="code">
<xsl:copy-of select="."/>
</xsl:variable>
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:value-of select="($invoice/node()/InvoiceLine/Service[ServiceMnemonicCode=$code]/ServiceDescription)[1]"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</xsl:template>