Smartest way to switch attribute sets in XSLT - 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>

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.

Prevent XSL loop from printing every value

I need to get the servtypes and servamt value when servtypes is equal to "Service 3". I used the XSL logic and it somehow gets the "Service 3" and its corresponding amount.
XSL Code:
<xsl:for-each select="service">
<fo:table-row>
<fo:table-cell padding-left="14pt" display-align="center">
<fo:block>
<xsl:if test="servtypes='Service 3'">
<xsl:value-of select="servtypes"/>
</xsl:if>
</fo:block>
</fo:table-cell>
<fo:table-cell text-align="left" >
<fo:block>
<xsl:if test="servtypes='Service 3'">
<xsl:value-of select="servamt"/>
</xsl:if>
</fo:block>
</fo:table-cell>
<fo:table-cell padding-left="14pt" display-align="center">
<fo:block>
<fo:leader/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
XML Example
<charges>
<fees>25</fees>
<desc>Description</desc>
<service>
<servtypes>Service 1</servtypes>
<servamt>150</servamt>
</service>
<service>
<servtypes>Service 2</sertypes>
<servamt>10</servamt>
</service>
<service>
<servtypes>Service 3</servtypes>
<servamt>150</servamt>
</service>
<charges>
The problem is that whenever it loops through different values of service it also creates table row for each service that doesn't match and the cell contains blank values see the table below.
How do I prevent creation of blank rows and cells and just create a table whose value matches only the Service 3?
<table>
<tr>
<td>Blank</td>
<td>Blank</td>
<td>Blank</td>
</tr>
<tr>
<td>Blank</td>
<td>Blank</td>
<td>Blank</td>
</tr>
<tr>
<td>Service 3</td>
<td>Service Value</td>
<td>Service</td>
</tr>
</table>
I was able to make this work check xsl below.
Solution
<xsl:for-each select="service">
<xsl:if test="servtypes='Service 3'">
<fo:table-row>
<fo:table-cell padding-top="2pt" padding-left="14pt" padding-bottom="2pt" display-align="center">
<fo:block>
Amount
</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="2pt" text-align="left" padding-bottom="2pt">
<fo:block>
<xsl:value-of select="servamt"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="2pt" padding-left="14pt" padding-bottom="2pt" display-align="center">
<fo:block><fo:leader/></fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:if>
</xsl:for-each>
The solution is simple: don't match what you don't need!
In the following code I put the matching expression in the xsl:template match="" rule and excluded the rest of the text() from matching. I also removed the xsl:ifs.
<xsl:template match="text()" />
<xsl:template match="/charges">
<xsl:apply-templates select="service" />
</xsl:template>
<xsl:template match="service[servtypes='Service 3']">
<fo:table-row>
<fo:table-cell padding-left="14pt" display-align="center">
<fo:block>
<xsl:value-of select="servtypes"/>
</fo:block>
</fo:table-cell>
<fo:table-cell text-align="left" >
<fo:block>
<xsl:value-of select="servamt"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding-left="14pt" display-align="center">
<fo:block>
<fo:leader/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</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>

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.

Keep header with first line of next block

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>