Skip to content
Working with Tables

Working with Tables

Basic structure

A table is built from <Table>, <Tr> (rows), and <Td> (cells):

<PlaceObject>
    <Table>
        <Tr>
            <Td><Paragraph><Value>Cell 1</Value></Paragraph></Td>
            <Td><Paragraph><Value>Cell 2</Value></Paragraph></Td>
        </Tr>
        <Tr>
            <Td><Paragraph><Value>Cell 3</Value></Paragraph></Td>
            <Td><Paragraph><Value>Cell 4</Value></Paragraph></Td>
        </Tr>
    </Table>
</PlaceObject>

Defining column widths

Without explicit widths, column widths are computed from their content, and the table shrinks to its content width. Use <Columns> for control:

<Table>
    <Columns>
        <Column width="3cm"/>
        <Column width="5cm"/>
        <Column width="*"/>
    </Columns>
    <!-- rows here -->
</Table>

Width options

Value Meaning
3cm, 50mm, 2in Fixed width
* Takes all remaining space
2* Takes twice the share of remaining space
20% Percentage of table width

Proportional columns are handy:

<Columns>
    <Column width="1*"/>  <!-- 1/4 -->
    <Column width="2*"/>  <!-- 2/4 -->
    <Column width="1*"/>  <!-- 1/4 -->
</Columns>

Table headers

<TableHead> defines rows at the top of the table. When the table breaks across frames and pages in a <Flow>, they repeat at the top of every part:

<Table>
    <Columns>
        <Column width="2cm"/>
        <Column width="*"/>
        <Column width="3cm"/>
    </Columns>
    <TableHead>
        <Tr>
            <Td><Paragraph><Value>ID</Value></Paragraph></Td>
            <Td><Paragraph><Value>Product</Value></Paragraph></Td>
            <Td><Paragraph><Value>Price</Value></Paragraph></Td>
        </Tr>
    </TableHead>
    <ForAll select="product">
        <Tr>
            <Td><Paragraph><Value select="@id"/></Paragraph></Td>
            <Td><Paragraph><Value select="@name"/></Paragraph></Td>
            <Td><Paragraph><Value select="@price"/></Paragraph></Td>
        </Tr>
    </ForAll>
</Table>

<TableFoot> is the counterpart at the bottom: its rows close the table and repeat at the bottom of every part.

Tables across pages

A table placed with <PlaceObject> is one object: it is kept whole and moves to the next frame or page when it does not fit. A table taller than every frame of its area is an error. To let a table break between its rows, put it into a <Flow>:

<Record match="catalog">
    <Flow>
        <Table>
            <TableHead>
                <Tr><Td><Paragraph><Value>Product</Value></Paragraph></Td></Tr>
            </TableHead>
            <ForAll select="product">
                <Tr><Td><Paragraph><Value select="@name"/></Paragraph></Td></Tr>
            </ForAll>
        </Table>
    </Flow>
</Record>

The rows are laid out down the frame. When the next row does not fit, the table continues in the next frame of the area, or on a new page, with the rows of <TableHead> and <TableFoot> repeated. A row itself never breaks, and the rows that a cell with rowspan joins go to the next frame together. Paragraphs and other tables can stand before and after the table in the same flow; see Flowing Text for how the flow fills the page.

Spanning rows and columns

<!-- Column span -->
<Td colspan="3">
    <Paragraph><Value>This spans 3 columns</Value></Paragraph>
</Td>

<!-- Row span -->
<Td rowspan="2">
    <Paragraph><Value>Spans 2 rows</Value></Paragraph>
</Td>

Styling tables with CSS

Tables support full CSS styling:

<StyleSheet>
    table { font-family: sans; font-size: 10pt; border-collapse: collapse; }
    thead { font-weight: bold; background-color: #e0e0e0; }
    td { padding: 4pt 8pt; border-bottom: 0.5pt solid #ccc; }
    tr:nth-child(even) { background-color: #f8f8f8; }
</StyleSheet>

Tables follow the two border models of CSS. The default, border-collapse: separate, keeps the cells apart by border-spacing (2pt unless set), and every cell draws all of its borders, so backgrounds and lines show small gaps between the cells. border-collapse: collapse draws the border between two cells once, the wider one wins, and the cells touch. Use it for tables with ruled or shaded rows, as in the examples on this page.

Cell alignment

<StyleSheet>
    .right { text-align: right; }
    .center { text-align: center; }
    .top { vertical-align: top; }
    .middle { vertical-align: middle; }
</StyleSheet>

<Td class="right"><Paragraph><Value>99.95</Value></Paragraph></Td>

Row height

A CSS height on a row or a cell is the minimum height of the row, as in a browser. A row with less content grows to that height and vertical-align places the content in the extra room. A row whose cells need more room keeps its natural height, nothing is clipped. The height counts the whole cell including padding and borders.

<StyleSheet>
    tr { height: 8mm; }
</StyleSheet>

<Tr style="height: 2cm"><Td><Paragraph><Value>Tall row</Value></Paragraph></Td></Tr>

For rows of exactly one height, as on a form or a label sheet, use -bag-fixed-height on the row. The row is then exactly that high, whatever its cells hold: content that does not fit draws past the row, and the row never breaks inside.

<Tr style="-bag-fixed-height: 16pt"><Td><Paragraph><Value select="@name"/></Paragraph></Td></Tr>

Tabular numbers for financial data

<StyleSheet>
    .numbers {
        text-align: right;
        font-feature-settings: "tnum", "lnum";
    }
</StyleSheet>

Cell content

Table cells can hold more than just text:

<!-- Multiple paragraphs -->
<Td>
    <Paragraph><Value>First line</Value></Paragraph>
    <Paragraph><Value>Second line</Value></Paragraph>
</Td>

<!-- Images -->
<Td>
    <Image href="logo.pdf" width="2cm"/>
</Td>

<!-- Nested tables -->
<Td>
    <Table>
        <Tr><Td><Paragraph><Value>Nested</Value></Paragraph></Td></Tr>
    </Table>
</Td>

<!-- Lists -->
<Td>
    <Ul>
        <Li><Value>Item 1</Value></Li>
        <Li><Value>Item 2</Value></Li>
    </Ul>
</Td>

A paragraph in a cell keeps its own margins, background and border, so a class such as .note { margin-left: 1em; background-color: #eef; } looks the same in a cell as on the page.

Individual cells are never split across pages – they’re always rendered as a single rectangular box. A table in a <Flow> breaks between rows, see Tables across pages.

Data-driven tables

Generate tables from XML data using <ForAll>:

<inventory>
    <item sku="A001" name="Widget" qty="150" price="9.99"/>
    <item sku="A002" name="Gadget" qty="75" price="24.99"/>
    <item sku="A003" name="Gizmo" qty="200" price="14.99"/>
</inventory>
<StyleSheet>
    table { font-family: sans; font-size: 10pt; border-collapse: collapse; }
    thead { font-weight: bold; background-color: #333; color: white; }
    td { padding: 4pt 8pt; border-bottom: 0.5pt solid #ddd; }
    .right { text-align: right; }
</StyleSheet>

<Record match="inventory">
    <PlaceObject>
        <Table>
            <Columns>
                <Column width="2cm"/>
                <Column width="*"/>
                <Column width="2cm"/>
                <Column width="2.5cm"/>
            </Columns>
            <TableHead>
                <Tr>
                    <Td><Paragraph><Value>SKU</Value></Paragraph></Td>
                    <Td><Paragraph><Value>Product</Value></Paragraph></Td>
                    <Td class="right"><Paragraph><Value>Qty</Value></Paragraph></Td>
                    <Td class="right"><Paragraph><Value>Price</Value></Paragraph></Td>
                </Tr>
            </TableHead>
            <ForAll select="item">
                <Tr>
                    <Td><Paragraph><Value select="@sku"/></Paragraph></Td>
                    <Td><Paragraph><Value select="@name"/></Paragraph></Td>
                    <Td class="right"><Paragraph><Value select="@qty"/></Paragraph></Td>
                    <Td class="right"><Paragraph><Value select="concat(@price, ' EUR')"/></Paragraph></Td>
                </Tr>
            </ForAll>
        </Table>
    </PlaceObject>
</Record>

Conditional row styling

Apply different styles based on data values:

<StyleSheet>
    .low-stock { background-color: #ffcccc; }
    .in-stock { background-color: #ccffcc; }
</StyleSheet>

<ForAll select="item">
    <Tr class="{ if (@qty &lt; 100) then 'low-stock' else 'in-stock' }">
        <!-- cells -->
    </Tr>
</ForAll>

Or use <Switch>/<Case> inside cells for more complex logic:

<Td>
    <Switch>
        <Case test="@qty &lt; 100">
            <Paragraph style="color: red;"><Value select="@qty"/></Paragraph>
        </Case>
        <Otherwise>
            <Paragraph><Value select="@qty"/></Paragraph>
        </Otherwise>
    </Switch>
</Td>

Table width

<!-- Fixed width -->
<Table width="15cm"><!-- ... --></Table>

<!-- Grid cells -->
<Table width="10"><!-- ... --></Table>

<!-- Full available width via CSS -->
<StyleSheet>
    table { width: 100%; }
</StyleSheet>

A margin-left moves the table to the right, with its borders, also when it is split across pages.

See also