XSL-FO — XML Formatting Objects for PDF Publishing, Layout, Tables, Page Masters
XML documents need to be rendered as PDFs, print-ready documents, or high-quality formatted output. XSL-FO (XML Formatting Objects) is the standard for XML-to-PDF publishing. This guide teaches XSL-FO page layout, block/inline formatting, tables, lists, and PDF generation with Apache FOP.
What You’ll Learn
You will design XSL-FO page masters, format text with block and inline elements, create tables with headers and spanning, build lists, generate PDFs from XML using Apache FOP, and apply XSL-FO in real-world document publishing. DodaZIP uses XSL-FO for generating formatted documentation and reports from XML configuration data.
Learning Path
flowchart LR
A[XSLT] --> B[XML Signatures]
B --> C[XSL-FO<br/>You are here]
C --> D[XML Publishing Pipeline]
style C fill:#f90,color:#fff
XSL-FO Document Structure
An XSL-FO document defines pages, flows, and formatting:
<?xml version="1.0" encoding="UTF-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="page"
page-width="8.5in" page-height="11in"
margin-top="0.75in" margin-bottom="0.75in"
margin-left="1in" margin-right="1in">
<fo:region-body margin-top="0.5in"/>
<fo:region-before extent="0.5in"/>
<fo:region-after extent="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="page">
<fo:static-content flow-name="xsl-region-before">
<fo:block text-align="center"
font-size="10pt" font-style="italic">
DodaTech Documentation
</fo:block>
</fo:static-content>
<fo:static-content flow-name="xsl-region-after">
<fo:block text-align="center" font-size="9pt">
Page <fo:page-number/>
</fo:block>
</fo:static-content>
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="24pt" font-weight="bold"
space-after="12pt">
XML Publishing Guide
</fo:block>
<fo:block font-size="12pt" line-height="1.4"
space-after="6pt">
XSL-FO transforms XML into formatted pages
suitable for PDF generation.
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>Page Masters
Multiple pages with different layouts:
<fo:layout-master-set>
<!-- Cover page: no margins -->
<fo:simple-page-master master-name="cover"
page-width="8.5in" page-height="11in"
margin="0in">
<fo:region-body margin="0in"/>
</fo:simple-page-master>
<!-- Normal page: with margins -->
<fo:simple-page-master master-name="normal"
page-width="8.5in" page-height="11in"
margin-top="0.75in" margin-bottom="0.75in"
margin-left="1in" margin-right="1in">
<fo:region-body margin-top="0.5in"/>
<fo:region-before extent="0.5in"/>
<fo:region-after extent="0.5in"/>
</fo:simple-page-master>
<!-- Landscape page for tables -->
<fo:simple-page-master master-name="landscape"
page-width="11in" page-height="8.5in"
margin="0.75in">
<fo:region-body/>
</fo:simple-page-master>
<!-- Page sequence master for alternating -->
<fo:page-sequence-master master-name="document">
<fo:repeatable-page-master-alternatives>
<fo:conditional-page-master-reference
master-reference="cover"
page-position="first"/>
<fo:conditional-page-master-reference
master-reference="normal"
page-position="rest"/>
<fo:conditional-page-master-reference
master-reference="landscape"
odd-or-even="even"/>
</fo:repeatable-page-master-alternatives>
</fo:page-sequence-master>
</fo:layout-master-set>Block Elements
<!-- Paragraph with indentation -->
<fo:block text-indent="0.25in"
space-before="6pt" space-after="6pt"
text-align="justify">
This paragraph has first-line indentation and
justified alignment with spacing before and after.
</fo:block>
<!-- Headings with varying sizes -->
<fo:block font-size="18pt" font-weight="bold"
font-family="Helvetica" space-before="12pt"
space-after="6pt" keep-with-next="always">
Section Heading
</fo:block>
<fo:block font-size="14pt" font-weight="bold"
space-before="8pt" space-after="4pt">
Subsection Heading
</fo:block>
<!-- Code block with monospace font -->
<fo:block font-family="Courier" font-size="9pt"
background-color="#f0f0f0" padding="6pt"
border="1pt solid #ccc" white-space="pre"
linefeed-treatment="preserve">
<?xml version="1.0"?>
<root>
<element/>
</root>
</fo:block>Inline Elements
<fo:block>
Text with
<fo:inline font-weight="bold">bold</fo:inline>,
<fo:inline font-style="italic">italic</fo:inline>,
<fo:inline font-size="8pt">small</fo:inline>,
<fo:inline color="red">colored</fo:inline>,
<fo:inline text-decoration="underline">underlined</fo:inline>,
and
<fo:inline background-color="yellow">highlighted</fo:inline>
formatting.
</fo:block>Tables
<fo:table table-layout="fixed" width="100%"
border-collapse="collapse">
<!-- Column widths -->
<fo:table-column column-width="30%"/>
<fo:table-column column-width="50%"/>
<fo:table-column column-width="20%"/>
<!-- Table header -->
<fo:table-header font-weight="bold"
background-color="#e0e0e0">
<fo:table-row>
<fo:table-cell padding="4pt"
border="0.5pt solid #999">
<fo:block>Feature</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #999">
<fo:block>Description</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #999"
text-align="center">
<fo:block>Version</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<!-- Table body -->
<fo:table-body>
<fo:table-row>
<fo:table-cell padding="4pt"
border="0.5pt solid #999">
<fo:block>XML Signing</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #999">
<fo:block>Digital signature support</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #999"
text-align="center">
<fo:block>2.0+</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell padding="4pt"
border="0.5pt solid #999">
<fo:block>XSL-FO</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #999">
<fo:block>PDF rendering engine</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #999"
text-align="center">
<fo:block>1.1</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
<!-- Cell spanning multiple columns -->
<fo:table-cell number-columns-spanned="3"
padding="4pt" border="0.5pt solid #999"
background-color="#fffde7">
<fo:block font-weight="bold" text-align="center">
This cell spans all three columns
</fo:block>
</fo:table-cell>
<!-- Cell spanning multiple rows -->
<fo:table-cell number-rows-spanned="2">
<fo:block>Vertical merge</fo:block>
</fo:table-cell>Lists
<fo:list-block space-before="6pt" space-after="6pt"
provisional-distance-between-starts="0.3in"
provisional-label-separation="0.1in">
<!-- Bullet list item -->
<fo:list-item>
<fo:list-item-label end-indent="label-end()">
<fo:block>•</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block>First item in the bullet list</fo:block>
</fo:list-item-body>
</fo:list-item>
<!-- Numbered list item -->
<fo:list-item>
<fo:list-item-label end-indent="label-end()">
<fo:block>1.</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block>Second item with numbering</fo:block>
</fo:list-item-body>
</fo:list-item>
<fo:list-item>
<fo:list-item-label end-indent="label-end()">
<fo:block>2.</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block>Third item in this list</fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>Generating PDF with Apache FOP
import org.apache.fop.apps.*;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
public class FopPdfGenerator {
public static void generatePdf(File foFile, File pdfFile)
throws Exception {
// Configure FOP
FopFactory fopFactory = FopFactory.newInstance(
new File(".").toURI());
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
// Create output stream
try (OutputStream out = new FileOutputStream(pdfFile)) {
// Construct FOP with PDF output
Fop fop = fopFactory.newFop(
MimeConstants.MIME_PDF, foUserAgent, out);
// Transform FO file to PDF
TransformerFactory factory =
TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
Source src = new StreamSource(foFile);
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(src, res);
System.out.println("PDF generated: " + pdfFile);
}
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Usage: java FopPdfGenerator in.fo out.pdf");
return;
}
generatePdf(new File(args[0]), new File(args[1]));
}
}XML to PDF Pipeline
Combine XSLT and FOP to transform raw XML into PDF:
<?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="page"
page-width="8.5in" page-height="11in"
margin="1in">
<fo:region-body/>
<fo:region-before extent="0.5in"/>
<fo:region-after extent="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="page">
<fo:static-content flow-name="xsl-region-before">
<fo:block text-align="right"
font-size="9pt" color="#666">
<xsl:value-of select="document/title"/>
</fo:block>
</fo:static-content>
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="20pt" font-weight="bold"
space-after="12pt">
<xsl:value-of select="document/title"/>
</fo:block>
<xsl:apply-templates select="document/section"/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="section">
<fo:block font-size="14pt" font-weight="bold"
space-before="12pt" space-after="6pt">
<xsl:value-of select="@title"/>
</fo:block>
<xsl:apply-templates select="paragraph"/>
</xsl:template>
<xsl:template match="paragraph">
<fo:block space-after="6pt" text-align="justify">
<xsl:value-of select="."/>
</fo:block>
</xsl:template>
</xsl:stylesheet>Run the pipeline:
# Step 1: Transform XML to XSL-FO
java -cp saxon9he.jar net.sf.saxon.Transform \
-s:input.xml -xsl:xml-to-fo.xsl -o:output.fo
# Step 2: Generate PDF from XSL-FO
java -cp fop.jar:lib/* org.apache.fop.cli.Main \
-fo output.fo -pdf output.pdf
# Combined one-liner
java -cp saxon9he.jar net.sf.saxon.Transform \
-s:input.xml -xsl:xml-to-fo.xsl \
| java -cp fop.jar:lib/* org.apache.fop.cli.Main \
-fo - -pdf output.pdfCommon XSL-FO Mistakes
1. Not Setting white-space for Code Blocks
Without white-space="pre" and linefeed-treatment="preserve", code blocks collapse whitespace and lose indentation and line breaks.
2. Missing Column Definitions
Tables without <fo:table-column> widths default to equal-width columns, which rarely produces the desired layout. Always specify column widths.
3. Overlapping Regions
If <fo:region-body> margin-top overlaps with <fo:region-before> extent, content collides. Ensure region extents are within the body margin.
4. Forgetting keep-with-next on Headings
Headings that appear alone at the bottom of a page confuse readers. Add keep-with-next="always" to headings to keep them with their following paragraph.
5. Incorrect Namespace URI
The XSL-FO namespace is http://www.w3.org/1999/XSL/Format. The xsl prefix is for XSLT (http://www.w3.org/1999/XSL/Transform). Confusing them causes rendering failures.
6. Using Unsupportable CSS Properties
XSL-FO is not CSS. Properties like display, margin, or padding don’t work. Use XSL-FO equivalents: space-before, space-after, padding, border.
7. Page Number References Across Sequences
Using <fo:page-number-citation> to reference page numbers in different page sequences requires careful setup. Ensure the referenced ID is unique and accessible.
Practice Questions
1. What namespace does XSL-FO use?
http://www.w3.org/1999/XSL/Format. The prefix fo: is conventional.
2. How do you create a multi-column layout in XSL-FO?
Use <fo:region-body column-count="2" column-gap="0.25in"/> on the page master, or use <fo:block span="all"> for full-width elements.
3. What is the difference between fo:region-body, fo:region-before, fo:region-after? region-body is the main content area. region-before is the top/header area. region-after is the bottom/footer area. Each has its own flow name.
4. How do you handle page breaks in XSL-FO?
Use page-break-before="always", page-break-after="always", or page-break-inside="avoid" on blocks to control pagination.
5. Challenge: Design an XSL-FO template for a product catalog with a cover page, table of contents, product listings with images and prices, and page numbers.
Answer: Use a page sequence master with cover (first), table of contents (roman numerals), and body (arabic numerals) page masters. Apply XSLT templates for each section. Use keep-with-next for headings and avoid orphans/widows with widows="2" orphans="2".
Mini Project: XML Invoice to PDF
<?xml version="1.0" encoding="UTF-8"?>
<invoice number="INV-2026-001" date="2026-06-20">
<bill-to>
<name>Acme Corp</name>
<address>123 Main St, City</address>
</bill-to>
<items>
<item>
<description>DodaZIP Enterprise License</description>
<quantity>5</quantity>
<unit-price>299.00</unit-price>
</item>
<item>
<description>Durga Antivirus Pro - 1yr</description>
<quantity>10</quantity>
<unit-price>49.99</unit-price>
</item>
</items>
</invoice>XSL-FO output:
<?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="invoice"
page-width="8.5in" page-height="11in"
margin="0.75in">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="invoice">
<fo:flow flow-name="xsl-region-body">
<!-- Header -->
<fo:block font-size="24pt" font-weight="bold"
color="#1a237e" space-after="4pt">
INVOICE
</fo:block>
<fo:block font-size="10pt" color="#666"
space-after="12pt">
Number: <xsl:value-of select="invoice/@number"/>
| Date: <xsl:value-of select="invoice/@date"/>
</fo:block>
<!-- Bill To -->
<fo:block font-weight="bold"
space-before="12pt" space-after="4pt">
Bill To:
</fo:block>
<fo:block>
<xsl:value-of select="invoice/bill-to/name"/>
</fo:block>
<fo:block space-after="12pt">
<xsl:value-of select="invoice/bill-to/address"/>
</fo:block>
<!-- Items Table -->
<fo:table width="100%" border-collapse="collapse">
<fo:table-column column-width="60%"/>
<fo:table-column column-width="15%"
text-align="center"/>
<fo:table-column column-width="25%"
text-align="right"/>
<fo:table-header font-weight="bold"
background-color="#1a237e" color="white">
<fo:table-row>
<fo:table-cell padding="6pt"
border="0.5pt solid #1a237e">
<fo:block>Description</fo:block>
</fo:table-cell>
<fo:table-cell padding="6pt"
border="0.5pt solid #1a237e">
<fo:block>Qty</fo:block>
</fo:table-cell>
<fo:table-cell padding="6pt"
border="0.5pt solid #1a237e">
<fo:block>Amount</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:for-each select="invoice/items/item">
<fo:table-row>
<fo:table-cell padding="4pt"
border="0.5pt solid #ccc">
<fo:block>
<xsl:value-of
select="description"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #ccc"
text-align="center">
<fo:block>
<xsl:value-of
select="quantity"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #ccc"
text-align="right">
<fo:block>
$<xsl:value-of
select="format-number(
quantity * unit-price,
'#,##0.00')"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
<!-- Total row -->
<fo:table-row font-weight="bold">
<fo:table-cell padding="4pt"
border="0.5pt solid #ccc">
<fo:block>Total</fo:block>
</fo:table-cell>
<fo:table-cell padding="4pt"
border="0.5pt solid #ccc"/>
<fo:table-cell padding="4pt"
border="0.5pt solid #ccc"
text-align="right">
<fo:block font-size="14pt">
$<xsl:value-of
select="format-number(
sum(invoice/items/item/
quantity *
unit-price),
'#,##0.00')"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
<!-- Footer -->
<fo:block text-align="center"
space-before="24pt" font-size="8pt"
color="#999">
Generated by DodaTech Publishing System
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>FAQ
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro