Keep header with first line of next block - xslt

At the end of the page, I dont want to have the label of 'examClin' isolated. So if ever, the label arrives at the end of the page, I need ONE and no more than one line of examClin to be attached with the #label of examClin... Or both elements should go to next page.
Am i clear enough?
different elements... we arrive at the end of the page
<fo:table-row>
<fo:table-cell number-columns-spanned="5">
<fo:block space-before="2mm">
<xsl:value-of select="./examClin/#label"/>: </fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell number-columns-spanned="5" padding-top="2mm" padding-bottom="2mm"
padding-left="1mm" padding-right="1mm">
<fo:block white-space-collapse="false" font-style="italic" >
<xsl:value-of select="./examClin/child::text()"/>
</fo:block>
</fo:table-cell>
</fo:table-row>

Put them into a single block (that means you must merge the two table rows into one) and use keep-together.

Thank you Aaron. But then I am afraid that if is a very long text, everything will keep together and not just the first line. As a result, it can leave a long white block on previous page.
I created the following template: the idea is to find what the first line will be: either the 75 first characters but if we find a return carriage before the 75 first characters, we will take the string before the first return carriage.
<xsl:template name="elem3">
<xsl:choose>
<xsl:when test="child::text()">
<xsl:variable name="test0" select="substring(child::text(),1,100000)"/>
<xsl:variable name="test1" select="substring(child::text(),0,75)"/>
<xsl:variable name="test2" select="substring(child::text(),75,100000)"/>
<xsl:variable name="test3" select="substring-before($test2,' ')"/>
<xsl:variable name="test4" select="concat($test1,$test3)"/>
<xsl:variable name="test5" select="substring-after($test2,' ')"/>
<xsl:variable name="test6" select="substring-before($test1,'
')"/>
<xsl:variable name="test7" select="substring-after($test0,'
')"/>
<fo:table-row>
<fo:table-cell number-columns-spanned="5">
<fo:block space-before="2mm">
<fo:inline font-weight="bold"><xsl:value-of select="#label"/>: </fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<xsl:choose>
<xsl:when test="child::text()">
<fo:table-row keep-with-previous="always">
<fo:table-cell number-columns-spanned="6" padding-top="2mm" padding-left="1mm" padding-right="1mm">
<fo:block white-space-collapse="false" font-style="italic" >
<xsl:choose>
<xsl:when test="contains($test1,'
')"> <xsl:value-of select="$test6"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$test4"/></xsl:otherwise>
</xsl:choose>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell number-columns-spanned="5" padding-left="1mm" padding-right="1mm">
<fo:block white-space-collapse="false" font-style="italic" >
<xsl:choose>
<xsl:when test="contains($test1,'
')"><xsl:value-of select="$test7"/></xsl:when>
<xsl:otherwise> <xsl:value-of select="$test5"/></xsl:otherwise>
</xsl:choose>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:when>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:template>

Related

Smartest way to switch attribute sets in XSLT

I'm currently doing something like this, and it feels like really bad coding style. The structure of the <fo:table-row> and <fo:table-cell> elements is exactly the same, only the xsl:use-attribute-sets is different. What is the smartest way to switch the attribute sets? The XSL version is no limitation.
<xsl:template name="myTemplate">
<xsl:param name="number-of-parts" as="xs:integer"/>
<xsl:choose>
<xsl:when test="$number-of-parts <= 16">
<fo:table-row xsl:use-attribute-sets="__toc__mini__table__row__empty">
<fo:table-cell xsl:use-attribute-sets="__toc__mini__table__row__empty__cell">
<fo:block/>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="__toc__mini__table__row__empty__cell">
<fo:block/>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="__toc__mini__table__row__empty__cell">
<fo:block/>
</fo:table-cell>
</fo:table-row>
</xsl:when>
<!-- If more than 16 languages -->
<xsl:otherwise>
<fo:table-row xsl:use-attribute-sets="__toc__mini__table__row__empty__small">
<fo:table-cell xsl:use-attribute-sets="__toc__mini__table__row__empty__cell__small">
<fo:block/>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="__toc__mini__table__row__empty__cell__small">
<fo:block/>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="__toc__mini__table__row__empty__cell__small">
<fo:block/>
</fo:table-cell>
</fo:table-row>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The attribute-set mechanism is very static, as you have discovered. If you have a dynamic requirement, I think attribute sets are best avoided (actually, I hardly ever use them myself).
Do something like
<fo:table-cell>
<xsl:sequence select="f:my-attribute-sets('small')">
<fo:block/>
</fo:table-cell>
and generate the attributes from your f:my-attribute-sets function.
Something like this? (Untested because I don't have your input to test with.)
<xsl:template name="myTemplate">
<xsl:param name="number-of-parts" as="xs:integer"/>
<xsl:variable name="table_row_variable">
<xsl:choose>
<xsl:when test="$number-of-parts <= 16">__toc__mini__table__row__empty</xsl:when>
<xsl:otherwise>__toc__mini__table__row__empty__cell__small</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<fo:table-row xsl:use-attribute-sets="{$table_row_variable}">
<fo:table-cell xsl:use-attribute-sets="{$table_row_variable}">
<fo:block/>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="{$table_row_variable}">
<fo:block/>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="{$table_row_variable}">
<fo:block/>
</fo:table-cell>
</fo:table-row>
</xsl:template>

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

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>

How to generate a blank table cell in XSL?

why does the table cell disappear when there is no photo existing to generate?
i am using the following code which is not working. What do i need to change if i want to generate a blank table cell if there is no photo existing to generate?
<xsl:for-each select="...............">
<xsl:choose>
<xsl:when test="*">
<xsl:if test=".....">
<xsl:if test=".......">
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
<fo:block>
<fo:external-graphic src="url('{concat($FILEPATH,.....'])}')"
inline-progression-dimension.maximum="4.1cm" block-progression-dimension.maximum="4cm"
content-width="scale-to-fit" content-height= "scale-to-fit" scaling="uniform"/>
</fo:block>
</fo:table-cell>
</xsl:if>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
<fo:block>
<fo:leader/>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
Your have <xsl:when test="*"> ... is that node empty? If that is your outside test and passes, but the other IFs (you do not show) do not pass their test, your template yields nothing.
Breaking this down with comments:
<xsl:when test="*">
<xsl:if test=".....">
<!-- If this does not pass, you get nothing -->
<xsl:if test=".......">
<!-- If this does not pass, you get nothing -->
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
<fo:block>
<fo:external-graphic src="url('{concat($FILEPATH,.....'])}')"
inline-progression-dimension.maximum="4.1cm" block-progression-dimension.maximum="4cm"
content-width="scale-to-fit" content-height= "scale-to-fit" scaling="uniform"/>
</fo:block>
</fo:table-cell>
</xsl:if>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
<fo:block>
<fo:leader/>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
Kevin is right, given your example, your when clause is likely satisfied but if one of your if statements evaluates as false, you will get an empty table-cell. My advice is to add the conditions of your if statements to the when clause using logic operators such as AND/OR, for example, say the conditions in your template where like this...
<xsl:when test="$node = 'A'">
<xsl:if test="$node/child = 'B'">
<xsl:if test="not(contains($node/child,'C'))">
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
...
</fo:table-cell>
</xsl:if>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
<fo:block>
<fo:leader/>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
</xsl:choose>
Could be expressed as
<xsl:when test="$node = 'A' AND $node/child = 'B' AND not(contains($node/child,'C'))">
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
...
</fo:table-cell>
</xsl:when>
<xsl:otherwise>
<fo:table-cell border="solid" text-align="center" font-weight="bold" number-columns-spanned="1">
<fo:block>
<fo:leader/>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
</xsl:choose>
And in doing so, you will make sure that if any of those three logical conditions aren't met, that your otherwise block will be called and the leader should keep the cell from collapsing, rather than the when statement being called despite the fact that your logic conditions aren't technically satisfied, and winding up with an empty cell which will be collapsed by FOP by default. Hope this clears things up a bit for you.

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>