Abbey Workshop

XSLT: XSL-FO basics with FOP

There is a lot of information about XSLT on the Net, and it is getting more popular every day. But XSL is defined in two parts: eXtensible Style Language Transformations (XSLT) and eXtensible Style Language Formatting Objects (XSL-FO). XSL-FO allows you to transform XML into into print. Using an XSL-FO processor like FOP you can output XML to PDF, PostScript, PCL, or SVG.

This howto is by no means an all inclusive tutorial on XSL-FO (because there really is a lot to it, see the book below for more information), but it is meant to be a starting point. To use the source code in this example, you will need Ant 1.6.5 or later, the Apache Formatting Objects Processor (FOP) 0.20.5, and of course Java 1.5 to install the preceding two.

Let's get started. First, we need XML source to format. How about some Shakespeare? Below is the XML source for the Tempest.

View: tempest.xml

Using FOP from Ant

To use FOP from Ant, a task for FOP must be defined in the build.xml file. The Ant documentation on this is a bit confusing. You need to include a number of library .jar files in the classpath for the FOP task. However, the Ant documentation does not have the correct names for the files. The following example shows the correct settings for the versions identified earlier (FOP 0.20.5). If you are using a different version, the names of the library files may change.

build1.xml

   1 <project name="FOP-IT" default="build_all">
   2 
   3 <!-- Define FOP Task -->
   4 <property name="fop.dir" value="/homedir/ap/fop"/>
   5 <taskdef name="fop" classname="org.apache.fop.tools.anttasks.Fop">
   6   <classpath>
   7         <pathelement location="${fop.dir}/build/fop.jar"/>
   8         <pathelement location="${fop.dir}/lib/avalon-framework-cvs-20020806.jar"/>
   9         <pathelement location="${fop.dir}/lib/batik.jar"/>
  10     </classpath>
  11 </taskdef>
  12 
  13 <!-- Create an XSL:FO file and then generate a PDF  -->
  14 <target name="create-fo">
  15   <!-- Transform one module and its lessons and topicsinto an xsl:fo file -->
  16   <xslt basedir="." destdir="."
  17     style="bard_to_fo.xsl" includes="**/*.ply.xml" force="true">
  18     <mapper type="glob" from="*.ply.xml" to="*.fo" />
  19   </xslt>
  20 </target>
  21 
  22 <target name="create-pdf" depends="create-fo" description="Generates a single PDF file">
  23   <fop format="application/pdf" outdir="." messagelevel="debug">
  24         <fileset dir=".">
  25            <include name="*.fo"/>
  26         </fileset>
  27   </fop>
  28 </target>
  29 
  30 <!-- === Create HTML Version === -->
  31 <target name="create-html-play">
  32   <xslt basedir="." destdir="."
  33     style="bard_to_html.xsl" includes="**/*.ply.xml" force="true">
  34     <mapper type="glob" from="*.ply.xml" to="*.html" />
  35   </xslt>
  36 </target>
  37 
  38 
  39 <!-- === Build All === -->
  40 <!-- Call all the other XSLT templates to create a complete list of products. -->
  41 
  42 <target name="build_all" depends="create-pdf, create-html-play" description="Performs all the builds we want to create by default.">
  43 </target>
  44 
  45 </project>

Generating a pdf is a two step process. First, you must generate an XSL-FO file with a .fo file extension. The .fo files contain the XML markup that describes how the output text will look. Once this file is generated with XSLT, then the FOP processor takes this file as input and generates a PDF file. The tasks create-fo and create-pdf represent these steps.

Generating a .fo File

Next, take a look at the .xsl file that generates our XSL-FO file.

Listing for: bard_to_fo.xsl

   1 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   2 xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:kso="nothin">
   3 <xsl:output method="xml" indent="yes"/>
   4 <xsl:strip-space elements="h1 h2 body" />
   5 
   6 <xsl:variable name="Copyright">Copyright &#169; 2006 Example1 Co, Inc. All Rights Reserved</xsl:variable>
   7 
   8 <xsl:variable name="ChapterTitle">Plays by the Bard</xsl:variable>
   9 
  10 
  11 <xsl:template match="/">
  12   <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  13     <fo:layout-master-set>
  14       <fo:simple-page-master
  15         master-name="first"
  16         margin-right="0.25in" margin-left="0.25in"
  17         margin-bottom="0.25in"  margin-top="0.25in"
  18         page-width="8.5in"    page-height="11in"
  19       >
  20         <fo:region-before extent="0.5in" region-name="first-before" />
  21         <fo:region-after extent="0.5in" region-name="first-after" />
  22         <fo:region-start extent="0.5in" />
  23         <fo:region-end extent="0.5in" />
  24         <fo:region-body   margin-top="1.0in"  margin-bottom="1.0in"
  25                 margin-left="0.5in" margin-right="0.5in" 
  26         />
  27       </fo:simple-page-master>
  28       
  29       <fo:simple-page-master
  30         master-name="odd"
  31         margin-right="0.25in" margin-left="0.25in"
  32         margin-bottom="0.25in"  margin-top="0.25in"
  33         page-width="8.5in"    page-height="11in"
  34       >
  35         <fo:region-before extent="0.5in" region-name="odd-before"/>
  36         <fo:region-after extent="0.5in" region-name="odd-after"/>
  37         <fo:region-start extent="0.5in" />
  38         <fo:region-end extent="0.5in" />
  39         <fo:region-body   margin-top="1.0in"  margin-bottom="1.0in"
  40                 margin-left="0.5in" margin-right="0.5in" 
  41         />
  42       </fo:simple-page-master>
  43 
  44       <fo:simple-page-master
  45         master-name="even"
  46         margin-right="0.25in" margin-left="0.25in"
  47         margin-bottom="0.25in"  margin-top="0.25in"
  48         page-width="8.5in"    page-height="11in"
  49       >
  50         <fo:region-before extent="0.5in" region-name="even-before" />
  51         <fo:region-after extent="0.5in" region-name="even-after" />
  52         <fo:region-start extent="0.5in" />
  53         <fo:region-end extent="0.5in" />
  54         <fo:region-body   margin-top="1.0in"  margin-bottom="1.0in"
  55                 margin-left="0.5in" margin-right="0.5in" 
  56         />
  57       </fo:simple-page-master>
  58 
  59       <fo:simple-page-master
  60         master-name="blank"
  61         margin-right="0.25in" margin-left="0.25in"
  62         margin-bottom="0.25in"  margin-top="0.25in"
  63         page-width="8.5in"    page-height="11in"
  64       >
  65         <fo:region-before extent="0.5in" region-name="blank-before"/>
  66         <fo:region-after extent="0.5in" region-name="blank-after" />
  67         <fo:region-start extent="0.5in" />
  68         <fo:region-end extent="0.5in" />
  69         <fo:region-body   margin-top="1.0in"  margin-bottom="1.0in"
  70                 margin-left="0.5in" margin-right="0.5in" 
  71         />
  72       </fo:simple-page-master>
  73       
  74       <fo:page-sequence-master master-name="chapter">
  75         <fo:repeatable-page-master-alternatives>
  76           <fo:conditional-page-master-reference
  77             master-reference="first"
  78             page-position="first"
  79           />
  80 
  81           <fo:conditional-page-master-reference
  82             master-reference="odd"
  83             page-position="rest"
  84             odd-or-even="odd"
  85           />
  86 
  87           <fo:conditional-page-master-reference
  88             master-reference="even"
  89             page-position="rest"
  90             odd-or-even="even"
  91           />
  92 
  93           <fo:conditional-page-master-reference
  94             master-reference="blank"
  95             blank-or-not-blank="blank"
  96           />
  97         </fo:repeatable-page-master-alternatives>
  98       </fo:page-sequence-master>
  99     </fo:layout-master-set>
 100     <fo:page-sequence   master-reference="chapter"
 101               initial-page-number="auto"
 102               force-page-count="even"
 103     >
 104       <fo:static-content flow-name="odd-before">
 105         <fo:block text-align="right" font-family="Helvectica, Arial, sans-serif" font-size="10pt">
 106           <xsl:value-of select="$ChapterTitle" />
 107         </fo:block>
 108       </fo:static-content>
 109       <fo:static-content flow-name="odd-after">
 110         <fo:block text-align="right">
 111           Page <fo:page-number />
 112         </fo:block>
 113         <fo:block text-align="center" font-family="Helvectica, Arial, sans-serif" font-size="8pt">
 114           <xsl:value-of select="$Copyright" />
 115         </fo:block>
 116       </fo:static-content>
 117       <fo:static-content flow-name="even-before">
 118         <fo:block text-align="left" font-family="Helvectica, Arial, sans-serif" font-size="10pt">
 119           <xsl:value-of select="$ChapterTitle" />
 120         </fo:block>
 121       </fo:static-content>
 122       <fo:static-content flow-name="even-after">
 123         <fo:block text-align="left">
 124           Page <fo:page-number />
 125         </fo:block>
 126         <fo:block text-align="center" font-family="Helvectica, Arial, sans-serif" font-size="8pt">
 127           <xsl:value-of select="$Copyright" />
 128         </fo:block>
 129       </fo:static-content>
 130 
 131       <fo:static-content flow-name="first-before">
 132       </fo:static-content>
 133 
 134       <fo:static-content flow-name="first-after">
 135         <fo:block text-align="right">
 136           Page <fo:page-number />
 137         </fo:block>
 138         <fo:block text-align="center" font-family="Helvectica, Arial, sans-serif" font-size="8pt">
 139           <xsl:value-of select="$Copyright" />
 140         </fo:block>
 141       </fo:static-content>
 142 
 143       <fo:static-content flow-name="blank-before">
 144       </fo:static-content>
 145 
 146       <fo:static-content flow-name="blank-after">
 147         <fo:block text-align="right">
 148           Page <fo:page-number />
 149         </fo:block>
 150         <fo:block text-align="center" font-family="Helvectica, Arial, sans-serif" font-size="8pt">
 151           <xsl:value-of select="$Copyright" />
 152         </fo:block>
 153       </fo:static-content>
 154       
 155       <!-- Content flow starts here -->
 156       <fo:flow flow-name="xsl-region-body">
 157         <xsl:apply-templates />
 158       </fo:flow>
 159     </fo:page-sequence>
 160   </fo:root>
 161 </xsl:template>
 162 
 163 <!-- Templates -->
 164 <xsl:template match="PLAY">
 165   <fo:block font-family="Helvectica, Arial, sans-serif" font-size="32pt" space-before="20pt" space-after="8pt"><xsl:value-of select="TITLE"/></fo:block>
 166   <xsl:apply-templates select="ACT" />
 167 </xsl:template>
 168 
 169 <xsl:template match="ACT">
 170   <fo:block font-family="Helvectica, Arial, sans-serif" font-size="24pt" space-before="20pt" space-after="8pt"><xsl:value-of select="TITLE"/></fo:block>
 171   <xsl:apply-templates select="SCENE" />
 172 </xsl:template>
 173 
 174 <xsl:template match="SCENE">
 175   <fo:block font-family="Helvectica, Arial, sans-serif" font-size="18pt" space-before="20pt" space-after="8pt"><xsl:value-of select="TITLE"/></fo:block>
 176   <xsl:apply-templates select="SPEECH" />
 177 </xsl:template>
 178 
 179 <xsl:template match="SPEECH">
 180   <fo:block font-family="Helvectica, Arial, sans-serif" font-size="12pt" space-before="16pt" space-after="8pt" font-weight="bold">
 181     <xsl:value-of select="SPEAKER" />
 182   </fo:block>
 183   <xsl:apply-templates select="LINE" />
 184 </xsl:template>
 185   
 186 <xsl:template match="LINE">
 187   <fo:block font-family="Helvectica, Arial, sans-serif" font-size="12pt" space-after="4pt">
 188     <xsl:value-of select="." />
 189   </fo:block>
 190 </xsl:template>
 191 
 192 
 193 </xsl:stylesheet>

The first thing you will notice about the file is that XSL-FO is quite a verbose language. If you think about it, page layout is complex stuff so you need quite a bit of detail to describe things.

Take a look at the root template. If you have worked with a publishing tool like Framemaker, the markup will look pretty familiar to you. If you haven't, don't panic. This markup merely sets up the layout for the document as a whole. First you setup things like the width, height and margin of each page. In this example, I have setup master pages for odd numbered and even numbered pages. Further down, you will see that headers and footers are defined for each page type. The reason for this is you want to the text to appear in different parts of the page. For example, on an even numbered page you want the header left justified, on an odd number page, the header is right justified.

Further down you will see more typical templates for elements like ACT or SCENE. Most XSL-FO markup consists of blocks that describes the formatting for that piece of text. Typically, a block is a paragraph. If you need more granular formatting for words you can use inline elements for that purpose.

Summary

Well that's an overview. In each aspect of this howto there is a lot more detail that can be covered in each area. But if you download the source, you should have a place to get started. Below I have included a few more files for additional information. In addition, click here to download the PDF file for The Tempest.

Here is the .fo file for The Tempest.

View: tempest1.fo

Here is an XSL style sheet that generates an HTML. Just for compare and contrast purposes.

Listing for: bard_to_html.xsl

   1 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   2 xmlns:fo="http://www.w3.org/1999/XSL/Format">
   3 <xsl:output method="xml" indent="yes"/>
   4 <xsl:strip-space elements="h1 h2 body" />
   5 
   6 <xsl:variable name="Copyright">Copyright &#169; 2006 Example1 Co, Inc. All Rights Reserved</xsl:variable>
   7 
   8 
   9 <xsl:template match="/">
  10 <html>
  11 <head><title><xsl:value-of select="PLAY/TITLE"/></title></head>
  12 <body>
  13 <xsl:apply-templates />
  14 <hr/>
  15 <p align="center"><xsl:value-of select="$Copyright" /></p>
  16 </body>
  17 </html>
  18 </xsl:template>
  19 
  20 <!-- Templates -->
  21 <xsl:template match="PLAY">
  22   <h1><xsl:value-of select="TITLE"/></h1>
  23   <xsl:apply-templates select="ACT" />
  24 </xsl:template>
  25 
  26 <xsl:template match="ACT">
  27   <h2><xsl:value-of select="TITLE"/></h2>
  28   <xsl:apply-templates select="SCENE" />
  29 </xsl:template>
  30 
  31 <xsl:template match="SCENE">
  32   <h3><xsl:value-of select="TITLE"/></h3>
  33   <xsl:apply-templates select="SPEECH" />
  34 </xsl:template>
  35 
  36 <xsl:template match="SPEECH">
  37   <p><strong><em><xsl:value-of select="SPEAKER"/>:</em></strong><br/>
  38   <xsl:apply-templates select="LINE" />
  39   </p>
  40 </xsl:template>
  41 
  42 <xsl:template match="LINE">
  43 <xsl:value-of select="."/><br/>
  44 </xsl:template>
  45 
  46 </xsl:stylesheet>