XSL-FO- Specifying conditional visibility - xslt

I have an HTML table that varies according to its content. 1-3 columns hide. 1 column name changes. All depending on content.
I am creating a PDF version of this HTML table. The PDF version uses apache-FOP with fop v1.0. The PDF output contains 2 of the aforementioned tables on one page. I do not want to create an .xsl for every combination of possibilities. That's a lot of duplication and maintenance.
I can solve the column name change simply by passing the column name in with the XML content. But, conditional visibility of the columns seems to be a far more challenging task.
How can I setup conditional visibility? Is it possible?
I'm having difficulty just getting <fo:table-column visibility="collapse" /> to work. The data still displays with visility set to hidden or collapse. display="none" looked promising. But, the API doesn't show it as a valid property for a table-column.
If I can't conditionally hide a column then I'll need to produce 18 unique xsl files...
Currently, my tables are very basic. I do
<fo:block font-size="10pt">
<fo:table table-layout="fixed" width="100%" border-collapse="separate">
<fo:table-column />
<fo:table-column />
<fo:table-column />
<fo:table-column />
<fo:table-column />
<fo:table-column visibility="collapse" />
<fo:table-column />
<fo:table-column />
<fo:table-column />
<fo:table-column />
<fo:table-header>
<fo:table-cell border-width="0.2mm" border-style="solid">
<fo:block>Header 1</fo:block>
</fo:table-cell>
//...9 other headers (these should show/hide as needed)
</fo:table-header>
<fo:table-body>
<xsl:for-each select="Object/type/SomeItem/ProcedureItemCollection">
<fo:table-row>
<fo:table-cell border-width="0.2mm" border-style="solid" >
<fo:block>
<xsl:value-of select="content1"/>
</fo:block>
</fo:table-cell>
//...9 other cells...
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
XML
<Procedure>
<SiteName>Site1</SiteName>//If Site1, don't create column 2
//If Site2, don't create column 2,3,4
//If Site3, create all columns
<itemtype1>
<item><member1></member1><member2></member2></item>
<item><member1></member1><member2></member2></item>
</itemtype1>
<itemtype2>
<item><member1></member1><member2></member2></item>
<item><member1></member1><member2></member2></item>
</itemtype2>
</Procedure>
Doing it this way, I have little flexibility in creating the table. But, this is all I know how to do.

After a lot of tinkering it turns out that I can add/remove columns using xsl:when and a variable.
First create a variable
<xsl:variable name="SiteName" select="Procedure/SiteName" />
Then conditionally create the 3 elements of the table (column definition, header, body). Starting with the column definition...
<xsl:choose>
<xsl:when test="$SiteName = 'Site1'">
<fo:table-column />//column 2
</xsl:when>
</xsl:choose>
Then the header
<xsl:choose>
<xsl:when test="$SiteName = 'Site1'">
<fo:table-cell border-width="0.2mm" border-style="solid">
<fo:block>Column2</fo:block>
</fo:table-cell>
</xsl:when>
</xsl:choose>
Finally, the body
<xsl:choose>
<xsl:when test="$SiteName = 'Site1'">
<fo:table-cell border-width="0.2mm" border-style="solid" >
<fo:block>
<xsl:value-of select="column2value"/>
</fo:block>
</fo:table-cell>
</xsl:when>
</xsl:choose>

Related

How to wirte a part of <fo:table> in a function

I have to create many <fo:table-row> in a <fo:table-body>. I think it's not nice if I write almost 5 lines of code several times (Maybe 50 times) to create the rows.
Like this:
<fo:table-body>
<fo:table-row>
<fo:table-cell padding-bottom="6pt">
<fo:value-of select="row1"/>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell padding-bottom="6pt">
<fo:value-of select="row2"/>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell padding-bottom="6pt">
<fo:value-of select="row3"/>
</fo:table-cell>
</fo:table-row>
....
</fo:table-body>
I tried to write a function that writes the <fo:table-row> for me. And I have to call the function every time and pass a parameter.
<xsl:function name="fn:createRow">
<xsl:param name="string1"/>
<fo:table-row>
<fo:table-cell padding-bottom="6pt">
<fo:value-of select="$string1"/>
</fo:table-cell>
</fo:table-row>
</xsl:function>
And now my XSLT look like this.
<fo:table-body>
<fo:block>
<fo:value-of select="fn:createRow('row1')"/>
<fo:value-of select="fn:createRow('row2')"/>
</fo:block>
</fo:table-body>
But I get the error:
"fo:block" is not a valid child of "fo:table-body"!
But when I work without <fo:block> I get nothing in the PDF:
<fo:table-body>
<fo:value-of select="fn:createRow('row1')"/>
<fo:value-of select="fn:createRow('row2')"/>
</fo:table-body>
Is there any opportunity to do it?
Thanks!
fo:table-cell can contain one or more block-level FOs, including fo:block. (See https://www.w3.org/TR/xsl11/#fo_table-cell)
You don't show your XML, but if all of the row* elements are contained by one element, then in the template for that element, you could do something like:
<fo:table>
<fo:table-body>
<xsl:for-each select="*">
<fo:table-row>
<fo:table-cell padding-bottom="6pt">
<fo:block>
<!-- The row* element is the current element here. -->
<xsl:apply-templates />
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:for-each>
</fo:table-body>
</fo:table>
Alternatively, you could make a template for all of the row* elements:
<xsl:template match="*[starts-with(local-name(), 'row')]">
<fo:table-row>
...
(From this distance, it's not clear why the row elements need separate element names.)
When you know the names of the elements that you want to format, you can do:
<xsl:apply-templates select="row1, row2, row3" />
and:
<xsl:template match="row1 | row2 | row3">
<fo:table-row>
...
I think instead of <fo:value-of select="$string1"/> you want <xsl:value-of select="$string1"/>. I would also check whether a fo:table-cell allows inline content, it might be necessary to put a fo:block container in the cell which has the xsl:value-of element as a child.
Also, for the function calls, don't use <fo:value-of select="fn:createRow('row1')"/>, instead, use <xsl:sequence select="fn:createRow('row1')"/>.
Also, fn is a reserved prefix, for your own functions declare and use your own namespace (e.g. xmlns:mf="http://example.com/mf" and <xsl:function name="mf:createRow" ...>...</xsl:function>, then use <xsl:sequence select="mf:createRow('row1')"/>.
So an example of the function would be
<xsl:function name="mf:createRow">
<xsl:param name="input"/>
<fo:table-row>
<fo:table-cell padding-bottom="6pt">
<fo:block>
<xsl:value-of select="$input"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:function>
and you could call it as e.g.
<fo:table-body>
<xsl:sequence select="(1 to 3) ! ('Row ' || . ) ! mf:createRow(.)"/>
</fo:table-body>

How to add a continued label in a static table with XSL/XSL-FO?

I am using an xml data similar to the sample below. There is a list of possible risks, and each of them can have a name and a description.
<risks>
<freezeDeductible name="item1" description="desc 1"/>
<moneySecLimit name="item2" description="desc 2"/>
<unscheduledLimit name="item3" description="desc 3"/>
...
</risks>
The structure of my template looks as below:
<xsl:template name="displayRisks">
<fo:block>
<fo:table>
<fo:table-column column-width="proportional-column-width(33.333)" column-number="1"/>
<fo:table-column column-width="proportional-column-width(33.333)" column-number="2"/>
<fo:table-column column-width="proportional-column-width(33.333)" column-number="3"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block font-weight="bold">ALL RISKS</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block><xsl:value-of select="/risks/freezeDeductible/#name"/></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block><xsl:value-of select="/risks/freezeDeductible/#description"/></fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<fo:block> </fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block><xsl:value-of select="/risks/moneySecLimit/#name"/></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block><xsl:value-of select="/risks/moneySecLimit/#description"/></fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block>
</xsl:template>
From the above template, I am generating PDF outputs. Because of the risk description size, and the number of risks elements is unknown, the table can go over multiple one or pages.
On the column1 x row1, there is displayed on first page, the title - "ALL RISKS". It needs to be on the same line as first risk name and description, so cannot move it to the table-header.
Is there any way that, when the table gets to page2, page3, etc. , to display the title (ALL RISKS) on the first row of the new page, with a label - for example: (continue..) ?
For example:
Page1: should display the title, on first table-row, as follows: ALL RISKS
If there is a Page2: display the title on the first table-row on Pag2, as follows: ALL RISKS(continued..)
Thanks!
There is an fo:retrieve-table-marker example in the 'XSL-FO Samples Collection' at https://www.antennahouse.com/xsl-fo-samples#table-retrieve-table-marker-1. (You may need to scroll up after you follow the link because of the banner on the page.)
There's also an example of the axf:repeat-content-at-break and axf:table-cell-repeated-marker AH Formatter extensions at https://www.antennahouse.com/xsl-fo-samples#axf-table-cell-repeated-marker-1 that demonstrates both the repetition and a '(Continued)' marker like what you want.

XSL-FO 2.0: Prevent page-break inside table

I can't find a way to prevent a page break inside a table in the RTF output.
I've tried a lot of combinations of keep-together / keep-with-next but nothing worked for me. The actual version has a parent fo:block with the attribute keep-together.within-page="always" including the whole table.
The problem only occurs when a RTF is generated. The PDF is correct and no page break inside a table exists. The table has a header-row and 3 data-rows. In the RTF there is a page-break after the header-row and the first 2 data-rows. On the next page the header is repeated and the last data-row is generated.
It's very important that the tables don't include a page-break.
Here is the relevant XSLT-Stylesheet code:
<fo:block keep-together.within-page="always" >
<xsl:for-each select="block">
<xsl:call-template name="drawData"></xsl:call-template>
</xsl:for-each>
<fo:table text-align="center">
<xsl:for-each select="row[#type='declare'][1]/column">
<fo:table-column column-number="position()" border-style="solid" border-color="#000000" border-width="0.5pt">
<xsl:attribute name="column-width"><xsl:value-of select="#width"/></xsl:attribute>
</fo:table-column>
</xsl:for-each>
<xsl:if test="row[#type='header']">
<fo:table-header>
<fo:table-row keep-together.within-page="2" background-color="#0000FF" color="#FFFFFF">
<xsl:for-each select="row[#type='header'][1]/column/block">
<fo:table-cell border-style="solid" border-color="#000000" border-width="0.5pt">
<xsl:attribute name="number-columns-spanned">
<xsl:value-of select="count(../../../row[#type='declare']/column) div count(../../../row[#type='declare'])"/>
</xsl:attribute>
<xsl:call-template name="drawData"></xsl:call-template>
</fo:table-cell>
</xsl:for-each>
</fo:table-row>
</fo:table-header>
</xsl:if>
<fo:table-body>
<xsl:for-each select="row[not(#type='header')]">
<fo:table-row keep-together.within-page="2">
<xsl:for-each select="column/block">
<fo:table-cell border-style="solid" border-color="#000000" border-width="0.5pt">
<xsl:call-template name="drawData"></xsl:call-template>
</fo:table-cell>
</xsl:for-each>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
A screenshot of the relevant table:
At the moment (FOP version 2.1), the RTF output has a few limitations compared to the PDF output; in particular, it does not support keep properties.
The linked page states that
RTF output is currently unmaintained
and keeps are
supported by the RTF library but not tied into the RTFHandler
so, while it is probably unlikely that this feature will be fixed in future versions without external help, it could be relatively easy to implement it (in which case it would be a good idea to submit a patch).

setting up dynamic row height in xsl fo

I am using below line to achive dynamic row height i.e the height should match with the left column .
But I also need to split the row into different cells ? When I am using simple fo:block-cell attrbutes ,I am not getting dynamic row hight . How can achive both dynamic row hieght and cells ??
<fo:table-row display-align="center">
<xsl:for-each select="xalan:distinct(Number)">
<fo:table-cell block-progression-dimension="auto" >
<fo:block-container height="10mm">
<fo:block font-size="9pt" border-right-width="0.1mm" border-right-style="solid" border-right-color="red" >
<xsl:value-of select="">
<xsl:variable name="">
<xsl:value-of select="">
</xsl:variable>
<xsl:if test="">
<xsl:value-of select=""/>
</xsl:if>
</fo:block>
</fo:block-container>
</fo:table-cell>
snapshot
Update -
I think one way that it could be done is to insert a vertical line after every cell value .Tried this , but somehow vertical line is not printing .
<fo:table-cell number-columns-spanned="2" xsl:use-attribute-sets="myBorder" display-align="center">
<fo:block font-size="10pt" text-align="center">
<fo:table>
<fo:table-body>
<fo:table-row>
<xsl:for-each select="../../../rateDetails[toGeography/sequence = $currentSequence]">
<fo:table-cell><!-- block-progression-dimension="auto" border-right-width="0.1mm" border-right-style="solid" border-right-color="black" text-align="center"> -->
<fo:block-container>
<fo:block font-size="9pt"><!-- border-right-width="0.1mm" border-right-style="solid" border-right-color="black" text-align="center"> -->
<xsl:call-template name="currencySymbol">
<xsl:with-param name="currencyCode" select="$currencyCode" />
</xsl:call-template>
<xsl:value-of select="util:formatCurrency(rate,$language,$countryCode)" />
</fo:block>
</fo:block-container>
<fo:block-container reference-orientation="90">
<fo:block>
<fo:leader leader-pattern="rule" leader-length="100%" rule-style="solid" rule-thickness="0.1mm" color="black"/>
</fo:block>
</fo:block-container>
</fo:table-cell>
</xsl:for-each>
Is there anything that I am missing for vertical line insertion .
snapshot2
If you move the border and padding properties to the fo:table-cell, the border will be the full height of the cell:
<fo:table-cell border-right="0.1mm solid red">
By setting fo:block-container/#height, you're probably finding that the text in a cell can overflow the 10mm but only the 10mm is being used to determine the row height. If you remove the fo:block-container, you should get a variable-height table row.
The new code sample makes things a bit clearer: you're using 2 nested tables.
That's another complication you don't need.
Just use 1 table.
In the first column, place all the geography codes into the first cell. It does not matter how many there are: if you have 1 geography code, the cell will be one line high. If you have 16 geography codes in this cell, the cell will automatically resize to be 3 lines high.
The rest of the row contains cells with the price information. On these cells, define the right border to generate the red vertical line.
<fo:table>
<fo:table-column column-width="..mm" column-number="1"/>
<fo:table-column column-width="..mm" column-number="2"/>
...you'll have to add some code here to add the correct number of columns to your table...
<fo:table-body>
<fo:table-row>
<fo:table-cell>
...place the code to insert the country codes here....
</fo:table-cell>
<xsl:for-each select="../../../rateDetails[toGeography/sequence = $currentSequence]">
<fo:table-cell block-progression-dimension="auto" border-right-width="0.1mm" border-right-style="solid" border-right-color="black" text-align="center">
<fo:block-container>
<fo:block font-size="9pt">
<xsl:call-template name="currencySymbol">
<xsl:with-param name="currencyCode" select="$currencyCode" />
</xsl:call-template>
<xsl:value-of select="util:formatCurrency(rate,$language,$countryCode)" />
</fo:block>
</fo:block-container>
</fo:table-cell>
</xsl:for-each>

Page break inside table XSL-FO

I have an XSL-FO stylesheet for a table.
<fo:page-sequence master-reference="defaultPage">
<fo:flow flow-name="xsl-region-body">
<fo:table table-layout="fixed" width="100%">
<fo:table-column column-width="9pt"/>
<fo:table-column column-width="30pt"/>
<fo:table-column column-width="150pt"/>
<fo:table-header>
<fo:table-row>
<fo:table-cell>
<fo:block><xsl:text>Column 1</xsl:text></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block><xsl:text>Column 2</xsl:text></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block><xsl:text>Column 3</xsl:text></fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<fo:table-row>
<xsl:apply-templates select="rbrOcjena"/>
<xsl:apply-templates select="sifPred"/>
<xsl:apply-templates select="nazPred"/>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
The table can have many rows, so I'd like to break it on new page when it comes to the end of current, when generating PDF. Also, I would like to repeat table header on new page, if that's possible. What attributes should I put in the table tag to make it so?
Thanks!
The table can have many rows, so I'd like to break it on new page when
it comes to the end of current
Without seeing your XSL-FO code, it is difficult to answer this. Please show it. But generally, this is done with keeps and breaks. For example:
<fo:table-row keep-together.within-page="always">
I would like to repeat table header on new page, if that's possible.
What attributes should I put in the table tag to make it so?
Instructing an XSL-FO processor to repeat a number of rows at the top of every page is not done via an attribute to fo:table. Instead, the rows that are to be repeated are put inside fo:table-header:
<fo:table-header>
<fo:table-row>
<fo:table-cell>
<!--Block and text content-->
</fo:table-cell>
</fo:table-row>
</fo:table-header>
Then, the default behaviour of the processor should be to repeat the header rows after a page break. That's because the omit-header-at-break attribute of fo:table is set to "false" by default.
The most obvious reason for this is that it is immediately clear which rows belong to the header and should thus be repeated. If this was just a reference in an attribute of fo:table it would be harder to identify multiple rows as the header. You will find the relevant part of the XSL specification here.
I've faced similar scenario...
Try below code...
<fo:table-row>
<xsl:if test="position() != 1">
<xsl:attribute name="break-before">page</xsl:attribute></i>
.
.
.