Tables
A table in XSL-FO is an area of content divided into rows and columns. A table is created with the fo:table element. A FO for a simple table is shown in Figure 14-1 and the output it creates is shown in Figure 14-2. This shows the basic structure of a fo:table element containing fo:table-body, fo:table-row and fo:table-cell elements.
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 2 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 2 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
The padding and border attributes are not inherited from containing elements, so are best defined on the fo:table-cell elements.
Cell padding
Padding is the amount of space that appears between the inside edge of the border of a cell and the outside edge of the content of the cell. Padding is specified by the padding attribute. The default amount of padding is '0pt'. Figure 14-3 shows a table with two cells. The first cell has padding="1pt" and the second has padding="5pt". Padding is almost always used to avoid having the content too close to the cell borders.
<fo:table keep-together.within-page="always">
<fo:table-body>
<fo:table-row>
<fo:table-cell border="1pt solid blue" padding="1pt">
<fo:block>this cell has padding set to '1pt' so the text is close to the edges of the cell</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid blue" padding="5pt">
<fo:block>this cell has padding set to '5pt' so the text is not so close to the edges of the cell</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
The padding attribute sets padding for all four sides of the cell. Individual sides can be set using the padding-left, padding-right, padding-top and padding-bottom attributes. The padding attribute also supports a shorthand format where:
- if one value is specified ( padding="2pt" ) the same value will apply to all four sides;
- if two values are specified ( padding="2pt 3pt" ) the first value will apply to the top and bottom edges, the second value to the left and right edges;
- if three values are specified ( padding="2pt 3pt 1pt" ) the first value will apply to the top edge, the second to the left and right edges, and the third to bottom edge;
- if four values are specified ( padding="2pt 3pt 1pt 0pt" ) these will apply to top, right, bottom and left edges in that order.
Cell background color
The background color of a cell is specified using the background-color attribute. This supports the same predefined colors as CSS and the use of hex values such as "#33ffcc". The background color of the cell extends to the inside edge of the border, which means that the area specified by the padding attribute is colored by the background color. If you do not want the background to extend to the edge of the padding, specify the background-color attribute on the contents of the cell (i.e. the fo:block elements) rather than on the fo:table-cell. An example FO for this is shown in Figure 14-5 and the resulting output appears in Figure 14-6.
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='1pt'>
<fo:block>
this cell has padding set to '1pt' so the text is close to the edges of the cell
</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='5pt' background-color='#dddddd'>
<fo:block background-color='#dddddd'>
this cell has padding set to '5pt' so the text is not so close to the edges of the cell
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
Cell background images
An image can be used as the background to a cell by specifying the background-image element, as shown in Figure 14-7.
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='1pt'>
<fo:block>
this cell has padding set to '1pt' so the text is close to the edges of the cell
</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='5pt' background-image='url(ibex.jpg)'>
<fo:block>
this cell has a background image
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
By default the image will be repeated if it is less than the width of the cell. This can be changed using the background-repeat attribute. This is set to "no-repeat" the output the image once. The background image can be positioned in the cell using the background-position-horizontal and background-position-vertical attributes.
Implicit and explicit rows
Usually FO files use the fo:table-row element to define which cells are in which rows, as shown in Figure 14-8.
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 2 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 2 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
However it is possible to dispense with the fo:table-row element and have the fo:table-body contain fo:table-cell elements directly.
In this case
any cell can have the ends-row attribute set to "true", which causes a new row to be started containing the next cell. This approach
is sometimes easier to use when generating the FO using XSLT, for example transforming a list of elements into a table
with multiple elements one each row.
Figure 14-9 shows what the above FO would look like if we changed it to use implicit rows.
<fo:table>
<fo:table-body>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt' ends-row='true'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 2 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 2 column 2</fo:block>
</fo:table-cell>
</fo:table-body>
</fo:table>
Table columns
The fo:table-column element is used to set the column width and other characteristics of a table column.
A fo:table-column element has an associated column number which determines which
column the fo:table-column element refers to. This column number is either implied (with the first fo:table-column element
applying to the first column, the second to the next etc.), or explicitly set using the column-number attribute.
A single fo:table-column element can be used to define the style of multiple columns by using the number-columns-spanned attribute.
Figure 14-10 shows the FO for a table with two fo:table-column elements, which apply to the first and second columns.
In this case they set the column widths (to 30% and 70%), and the give the second column a shaded background.
<fo:table>
<fo:table-column column-width='30%'/>
<fo:table-column column-width='70%' background-color='#dddddd'/>
<fo:table-body>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 2 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 2 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
Some cell attributes such as background color are determined using attributes from the cell itself and from the other elements of the table structure. The order of precedence in determining cell characteristics such as background-color is fo:table-cell, fo:table-row, fo:table-body, fo:table-column and finally fo:table.
Proportional column widths
Columns can be allocated widths which are proportional to the widths of other columns. For example, if we have two columns and
want to give the first column twice the width of the second, we can specify column widths using the proportional-column-width() function
as shown in Figure 14-11.
The total of the values used in the proportional-column-width() functions is 3 (2+1), so the
first column will gave 2/3 of the width and the second 1/3.
<fo:table>
<fo:table-column column-width='proportional-column-width(2)'/>
<fo:table-column column-width='proportional-column-width(1)' background-color='#dddddd'/>
<fo:table-body>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 2 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 2 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
Spanning columns and rows
The number of columns which a cell spans is set by the number-columns-spanned attribute. An example FO for this is shown in Figure 14-12. In this example the first cell of the first row spans two columns. The output from this FO appears in Figure 14-13.
<fo:table>
<fo:table-column column-width="30%"/>
<fo:table-column column-width="70%" background-color="#dddddd"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell border="1pt solid blue" padding="2pt" number-columns-spanned="2">
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 2 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid blue" padding="2pt">
<fo:block>row 2 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
The number of rows which a cell spans is set by the number-rows-spanned attribute. Example FO for this is shown in Figure 14-14. In this example the first cell of the first row spans two rows. The output from this FO appears in Figure 14-15.
<fo:table>
<fo:table-column column-width='30%'/>
<fo:table-column column-width='70%' background-color='#dddddd'/>
<fo:table-body>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt' number-rows-spanned='2'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 2 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
Table headers
Table headers are created using the fo:table-header element. The fo:table-header should appear inside the fo:table element after any fo:table-column elements and before any fo:table-body elements. The fo:table-header element is similar in structure to a fo:table-body element in that it contains fo:table-row elements.
This section describes the behavior of table headers which do not change. Headers which can have different content on different pages are described later in this chapter in the section on continuation markers on page continuation-markers. Figure 14-16 shows the FO for a simple table with a one row header and two content rows.
<fo:table>
<fo:table-column column-width="100%"/>
<fo:table-header>
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="5pt">
<fo:block>Heading</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="5pt">
<fo:block>row 1</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row border="1pt solid black" padding="5pt">
<fo:table-cell>
<fo:block>row 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
By default table headers are repeated at the top of the table after each page break. To prevent the table header appearing on pages after the first, specify table-omit-header-at-break = "true" on the fo:table element.
Table footers
Table footers are created using the fo:table-footer element. The fo:table-footer should appear inside the fo:table element after any fo:table-column and fo:table-header elements and before any fo:table-body elements. The fo:table-footer element is similar in structure to a fo:table-body element in that it contains fo:table-row elements.
It is a common error to place the fo:table-footer element at the end of the table, after the fo:table-body elements. It must be placed before the fo:table-body elements because Ibex may start rendering the table to PDF before the whole table has been read from the FO file. This section describes the behavior of table footers which do not change. Footers which can have different content on different pages are described later in this chapter in the section on continuation markers on page continuation-markers. Figure 14-17 shows the FO for a simple table with a one row header and footer and two content rows.
<fo:table>
<fo:table-column column-width="100%"/>
<fo:table-header>
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="5pt">
<fo:block>Heading</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-footer>
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="5pt">
<fo:block>Footer</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-footer>
<fo:table-body>
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="5pt">
<fo:block>row 1</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row border="1pt solid black" padding="5pt">
<fo:table-cell>
<fo:block>row 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
By default table footers are repeated at the bottom of the table before each page break. To prevent the table footer appearing on pages other than the last, specify table-omit-footer-at-break = "true" on the fo:table element.
Repeating headers
Table headers are repeated at the top of the table after each page break. This is the default. To prevent the table header appearing on pages after the first, specify table-omit-header-at-break = "true" on the fo:table element.
Repeating footers
Table footers are repeated at the bottom of the table before each page break. This is the default. To prevent the table footer appearing on pages other than the last, specify table-omit-footer-at-break = "true" on the fo:table element.
Repeating table borders
Table borders by default do not repeat at a break in the table, so the top border of a table is rendered only on the first page the table is on and the bottom border is rendered only on the last page.
To make the table bottom border repeat at each page break it is necessary to specify border-after-width.conditionality = "retain" on the table element.
To make the table top border repeat at each page break it is necessary to specify border-before-width.conditionality = "retain" on the table element.
Table continuation markers
Table continuation markers provide a way of dynamically changing the header and footer on a table so that different content can be displayed on different pages. A typical use of this feature is to put the words "continued on next page" in the footer of a table on all pages except the last. Here we examine how the "continued on next page" requirement can be satisfied using Ibex.
The approach taken by XSL-FO has two parts, implemented using the fo:marker and fo:retrieve-table-marker elements.
First a fo:retrieve-table-marker
element is added to the footer. When the PDF is created this element will be replaced by the contents of
one of the fo:marker elements which has the same value for the class attribute.
Which fo:marker element is chosen to appear in the table footer
depends on the values of the attributes on the fo:retrieve-table-marker.
The footer for this example is shown in Figure 14-18. As the PDF file is created the contents
of the fo:marker element with marker-class-name = "continued" will be located and inserted into the
fo:table-footer element. The content of the marker element must be valid FO elements for their position in the
table-footer. In this example the retrieved elements go directly under the table-footer element, so the
elements retrieved must be table-row elements.
<fo:table-footer>
<fo:retrieve-table-marker
retrieve-class-name="continued"
retrieve-position-within-table="first-starting"
retrieve-boundary-within-table="page"/>
</fo:table-footer>
Typically, there will be more than one fo:marker element which has the marker-class-name = "continued". If this is not the case then the footer content will never change. The retrieve-position attribute specifies which marker to retrieve. In this example we want the first marker which appears on the page, so we use retrieve-position = "first-starting-within-page". We also specify retrieve-boundary = "table" so any marker from any part of the table which has been output to PDF can be retrieved. Other options are detailed later in this section. Conceptually, Ibex looks at every row in the table which has been output to the PDF file (including rows on the current page), collects all the markers associated with each of those rows and selects one to go into the footer. Markers associated with rows which are not on either the current page or prior pages are not considered. It is possible to have a different marker associated with every row in the table.
This is useful for situations such as like rendering a running total. The second part of the process is to define one or more fo:marker elements. In this case our marker elements are associated with fo:table-row elements. The first table-row has a marker element which specifies the "continued on next page" text. The contents of this marker will be retrieved for all pages except the last.
The last row of the table has an empty marker element. The content of this (that is to say no rows) will be what appears in the footer on the last page of the table. The marker from the first row is shown in Figure 14-19 and the marker from the last row is shown in Figure 14-20.
<fo:table-row>
<fo:marker marker-class-name="continued">
<fo:table-row>
<fo:table-cell>
<fo:block>continued on next page/<fo:block>
</fo:table-cell>
</fo:table-row>
</fo:marker>
<fo:table-cell>
<fo:block>row 1 cell 1 /<fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:marker marker-class-name="continued"/>
<fo:table-cell>
<fo:block>row (last) cell 1 /<fo:block>
</fo:table-cell>
<fo:table-row>
Aligning columns at the decimal point
Ibex can align the contents of cells in a column on the decimal point by specifying text-align="." on each fo:table-cell in the column. This can be done explicity on each fo:table-cell, or to make things easier to maintain it can be done by specifying text-align="." on the fo:table-column and text-align="from-table-column" on each fo:table-cell.
Example FO for aligning columns is shown in Figure 14-21.
<fo:table font="10pt arial">
<fo:table-column column-width="50%" />
<fo:table-column column-width="50%" text-align="."/>
<fo:table-body>
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="3pt" >
<fo:block>ibexdls</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3pt" text-align="from-table-column()">
<fo:block>499.02</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="3pt">
<fo:block>Total</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3pt" text-align="from-table-column()" font-size="18pt">
<fo:block>499.00</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>