First row of a table on a new page - xslt

I have a requirement that the first row on that page of a table (not the header) is different from the rest. I can't figure out if it can be done with markers or if it's even possible with just FO.
I basically have a legend for a graphic that would be tagged something like:
<row>
<callout>1</callout>
<name>Cog</name>
<description>Super important for stuff</description>
</row>
<row>
<callout>2</callout>
<name>Sprocket</name>
<description>Not nearly as important for stuff</description>
</row>
<row>
<callout>3</callout>
<name>Sprigot</name>
<description>I'm not creative</description>
</row>
<row>
<callout>4</callout>
<name>Last One</name>
<description>Blah blah blah</description>
</row>
The table would output as (X is a count for the number of tables):
Callout
Name
Desc
X - 1
Cog
Super important for stuff
   - 2
Sprocket
Not nearly as important for stuff
   - 3
Sprigot
I'm not creative
page break
Callout
Name
Desc
X - 4
Last One
Blah blah blah
The count only shows up on the first row on that page, but may obviously show up multiple times if the table spans multiple pages. Is there any way to get it to output like that? I'm using XSL 2.0, Saxon 9, and Apache FOP 2.7.
<xsl:template match="table">
<fo:table>
<fo:table-column column-width="20%"/>
<fo:table-column column-width="30%"/>
<fo:table-column column-width="40%"/>
<fo:table-header>
<fo:table-row>
<fo:table-cell>
<fo:block>Callout</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Name</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Desc</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:apply-templates/>
</fo:table-body>
</fo:table>
</xsl:template>
<xsl:template match="row">
<fo:row>
<xsl:apply-templates/>
</fo:row>
</xsl:template>
<xsl:template match="callout">
<fo:table-cell>
<!-- The code needs to go here to get the count to appear only if this row is the first row on the page. -->
<fo:block>
<xsl:text> - </xsl:text>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
</xsl:template>
<xsl:template match="name">
<fo:table-cell>
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
</xsl:template>
<xsl:template match="description">
<fo:table-cell>
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:table-cell>
</xsl:template>

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>

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).

fo:block next to fo:table in xsl

how can I put a fo:block with a text like:
<fo:block text-align="right" font-size="48pt">
HALLO
</fo:block>
side by side / next to a my fo:table in the xsl file.
My XSL File looks so:
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:java="http://xml.apache.org/xslt/java" xmlns:date="java.util.Date" xmlns:sf="java.text.SimpleDateFormat"
exclude-result-prefixes="java" version="1.0">
....
....
...
<xsl:template match="Order">
<fo:table border="0.5pt solid">
<fo:table-column column-width="5cm"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block font-size="10pt">
delivery:
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block font-weight="bold" font-size="12pt">
<xsl:value-of select="#DeliveryTime"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:stylesheet>
Next to the table above in my xsl sheet I want to put the HALLO text.
If you really want this, you can specify a block-container that contains two block-containers with absolute positions placed side by side. The table goes in one, the text goes in the other.
But it's probably simpler to extend the table across the full page width and place the 'Hallo' text inside a cell, and span/merge cells if you need more room.

XSL to filter duplicates [duplicate]

This question already has answers here:
XSLT Removing duplicates
(4 answers)
Closed 9 years ago.
I am trying generate a PDF using XSL. But I dont want duplicates(it should not be displayed back to back) , only for value=Started.
Below is the XSL snippet where I display the query result (string_1 column).
<xsl:for-each select="root/query1/row">
<fo:table-row height="0.9cm">
<xsl:if test="event_name = 'Started'">
<fo:table-cell border-style="solid" border-width="0.5pt"
number-columns-spanned="5">
<fo:block font-family="Courier" color="Blue" font-size="10pt" font-weight="normal" text-align="center">
<xsl:value-of select="string_1"/>
</fo:block>
</fo:table-cell>
</xsl:if>
</xsl:for-each>
Eg,
My query1 may give results like:
String_1
======
Started
In Progress
Complete
Started
Started
In Progress
In Progress
Complete
My PDF should be
=========
Started
In Progress
Complete
Started
In Progress
In Progress
Complete
Sorry if I didn't provide much information. I am new to XSL.
I don't know, how your input XML exactly looks like, but you should be able to adapt from this XML
<root>
<query1>
<row>
<string_1>Started</string_1>
</row>
<row>
<string_1>In Progress</string_1>
</row>
<row>
<string_1>In Progress</string_1>
</row>
<row>
<string_1>In Progress</string_1>
</row>
<row>
<string_1>Complete</string_1>
</row>
</query1>
</root>
and this XSLT:
<xsl:template match="/">
<xsl:for-each select="root/query1/row">
#> <xsl:if test="not(preceding-sibling::*[1]=string_1)">
<fo:table-row height="0.9cm">
<!-- <xsl:if test="event_name = 'Started'"> -->
<fo:table-cell border-style="solid" border-width="0.5pt" number-columns-spanned="5">
<fo:block font-family="Courier" color="Blue" font-size="10pt" font-weight="normal" text-align="center">
<xsl:value-of select="string_1" />
</fo:block>
</fo:table-cell>
<!-- </xsl:if> -->
</fo:table-row>
#> </xsl:if>
</xsl:for-each>
</xsl:template>
I have highlighted the important lines by #>. I have commented out one of the if-statements which seems to be there for doing different formatting on started nodes.
preceding-sibling::*[1] gives you the last processed node. It is compared to string_1 which is the current node. If it is different (not()), it should become part of the output.

XSLT: Subtotals

Source XML:
<Root>
<Data>
<Code>A</Code>
<Value>10</Value>
</Data>
<Data>
<Code>A</Code>
<Value>10</Value>
</Data>
<Data>
<Code>B</Code>
<Value>10</Value>
</Data>
<Data>
<Code>A</Code>
<Value>2</Value>
</Data>
<Data>
<Code>C</Code>
<Value>10</Value>
</Data>
<Data>
<Code>A</Code>
<Value>5</Value>
</Data>
<Data>
<Code>B</Code>
<Value>4</Value>
</Data>
<Data>
<Code>A</Code>
<Value>10</Value>
</Data>
<Data>
<Code>C</Code>
<Value>10</Value>
</Data>
<Data>
<Code>B</Code>
<Value>10</Value>
</Data>
<Data>
<Code>A</Code>
<Value>10</Value>
</Data>
<Data>
<Code>C</Code>
<Value>5</Value>
</Data>
....
</Root>
XSL-FO Code:
My code(XSL-FO) contains 3 columns where each column contains the content of 'A', 'B', 'C'
<fo:table-body>
<xfd:table-row-repeat xpath="Root/Data" font-family="Arial Narrow" font-size="10pt" padding-after="0.55cm">
<xsl:if test="Code='A'">
<fo:table-cell>
<fo:block height="12pt">Value
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt" border="0.1pt solid black" text-align="center">
<xsl:value-of select="Value" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt">Points</fo:block>
</fo:table-cell>
</xsl:if>
</xfd:table-row-repeat>
</fo:table-body>
Same code for each columns to display values of 'B' & 'C'
In Table-footer i've to get these subtotal of 'A','B', 'C'
<fo:table-body>
<xfd:table-row-repeat xpath="Root/Data" font-family="Arial Narrow" font-size="10pt" padding-after="0.55cm">
<xsl:if test="Code='A'">
<fo:table-cell>
<fo:block height="12pt">SubTotal
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt" border="0.1pt solid black" text-align="center">
<--Here Sum of first 15 A's. if the A's or B's or C's exceed by 15, then the table flows to 2nd Page. In that case, 1st Page table-footer shows individual subtotals of first 15 A's, 15 B's and C's. In 2nd Page, the subtotals should contain Subtotal of first 15 A's+ Succeeding A's, in the same way B's and C's -->
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt">Points</fo:block>
</fo:table-cell>
</xsl:if>
</xfd:table-row-repeat>
</fo:table-body>
Here the XSL-FO code is shown for only one column(For Root/Data/Code='A'), the other 2 columns('B' & 'C') consists of same code.
Conditions in Detail:
Condition 1): when Root/Data/Code = 'A' or 'B' or 'C'
i need individual totals of 'A', 'B' and 'C' in Table-Footer individual Column.
Condition 2): inturn if individual count(Root/Data/Code) of 'A', 'B' & 'C' crosses 15. Then Page flows to 2nd Page then Table-Footer in 2nd Page needs to contains subtotal of first 15 A's + the sum of succeeding A's in the same way for B's And C's
i.e., if 20 A's, 10 B's and 25 C's are present in Source XML.
In 1st Page, Table-Footer
SubtotalI(Value of 15 A's)=
SubtotalII(Value 10 B's)=
SubtotalIII(Value 15 C's)=
In 2nd Page, Table-Footer
SubtotalI(15 A's+ next 5 A's)=
SubtotalII(Value 10 B's)= <!--No Change as count of B's is less than 15 -->
SubtotalIII(15 C's + next 10 C's)=
I'm trying this logic using xsl:key by grouping through Code tag for evaluating sum of 'A', 'B' and 'C'. As i'm new to XSLT i'm finding it too difficult to solve this logic using xsl:key. Can anyone help in solving this logic?
Thanks in Advance
There are several difficulties with what you try to achieve. Calculating subtotals at a given point is easiest actually. You just need the preceding axis and a sum to calculate it:
sum(preceding::Value[../Code = 'A'])
To include the current value as well, use the union operator like this:
sum(Value[../Code = 'A'] | preceding::Value[../Code = 'A'])
A bigger difficulty is to show a different table 'footer' on each page. Headers and footers are repeated on pages automatically, but the contents is the same across all pages which the table spans. The only solution I see is to break the table yourself, adding a different table footer each time.
By far the easiest way is to just take a fixed number of Data elements at a time, and displaying those in a separate table. You can loop through A, B and C types in one for-each, giving each value a separate row. That way the table always has the same number of rows. You can experiment with the number you can include to determine how many fit on one page.
The following code returns a table with the first 10 Data values. A, B and C values are positioned straight under each other, but you could position them left, middle, and right respectively if you like. At the bottom of the table three rows are added with subtotals for A, B and C.
<?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:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="global">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="global">
<fo:flow flow-name="xsl-region-body">
<fo:block>
<fo:table table-layout="fixed" width="150mm" border-style="solid">
<fo:table-column column-width="50mm"/>
<fo:table-column column-width="50mm"/>
<fo:table-column column-width="50mm"/>
<fo:table-body font-size="7pt">
<xsl:for-each select="/Root/Data[10 >= position()]">
<fo:table-row border-style="solid">
<fo:table-cell>
<fo:block height="12pt">
<xsl:value-of select="Code" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt" border="0.1pt solid black" text-align="center">
<xsl:value-of select="Value" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt">Points</fo:block>
</fo:table-cell>
</fo:table-row>
<xsl:if test="position() = last()">
<fo:table-row border-style="solid">
<fo:table-cell>
<fo:block height="12pt">Subtotal A</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt" border="0.1pt solid black" text-align="center">
<xsl:value-of select="sum(preceding::Value[../Code = 'A'] | Value[../Code = 'A'])" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt">Points</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row border-style="solid">
<fo:table-cell>
<fo:block height="12pt">Subtotal B</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt" border="0.1pt solid black" text-align="center">
<xsl:value-of select="sum(preceding::Value[../Code = 'B'] | Value[../Code = 'B'])" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt">Points</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row border-style="solid">
<fo:table-cell>
<fo:block height="12pt">Subtotal C</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt" border="0.1pt solid black" text-align="center">
<xsl:value-of select="sum(preceding::Value[../Code = 'C'] | Value[../Code = 'C'])" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block height="12pt">Points</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:if>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
You still need something to determine how many tables of n Data items you would need, and then do some recursive calls to output all of them. Hope this is sufficient for the moment to get you going again!
PS: I noticed you are using xfd prefix. That looks like you are working with the XF Designer from Ecrion. I am not very familiar with it. The above code is a plain XSLT 1.0 solution. Not sure it works in XF Designer, please let me know..