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>

Figure 14-1: FO for a simple 2 x 2 table

Figure 14-2: Simple 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>

Figure 14-4: FO showing cells with different padding

Figure 14-3: Different levels of padding on cells

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>

Figure 14-5: FO setting the background color on a fo:table-cell

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>

Figure 14-7: FO for using an image as a cell background

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>

Figure 14-8: Tables with cells contained in rows

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>

Figure 14-9: FO for a table with implicit rows

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>

Figure 14-10: FO using table-column elements

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>

Figure 14-11: FO using proportional column widths

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>

Figure 14-12: FO for cell spanning 2 columns

Figure 14-13: Cell spanning two columns

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>

Figure 14-14: FO for cell spanning two rows

Figure 14-15: Cell spanning two rows

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>

Figure 14-16: Simple table with header

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>

Figure 14-17: FO for simple table with header and footer

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>

Figure 14-18: FO for retrieve-table-marker

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>

Figure 14-19: FO for marker in the first 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>

Figure 14-20: FO for marker in the last 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>

Figure 14-21: FO for decimal point alignment

Figure 14-22: Output for decimal point alignment