Entering the world of DocBook XML, XSL, FOP, and more

April 2007

My first major project at eZ Systems was to edit and do layout for the book eZ Publish Content Management Basics (written by Bergfrid Marie Skaara).

eZ Publish is an Open Source Enterprise Content Management System that is used to build powerful, flexible web solutions, enabling people and organizations to share their information. Overall, this book provides a comprehensive introduction to writing, editing and managing content with eZ Publish.

I learned a lot of things working on this book. XSL? Heard of it, but didn’t really know what it did. DocBook, FOP? I’d never even heard of those terms.

In I dove, and a few months later, out came the book in a nice PDF. Make no mistake, this was a real team effort, with people like Bergfrid (the author mentioned above) and Jennifer Zickerman (my boss) playing key roles in the technical innerworkings of the book. But I’m the lucky one who gets to write about the process!

With this site, I hope to share what I learned about the whole DocBook production process. This is by no means a comprehensive guide to DocBook (those sorts of things exist!) but hopefully it will serve as a good source of information for those madly Googling (like I did) to find out how things are done.

Why use DocBook XML

Here’s a list of advantages to using DocBook XML:

How XML turns into PDF: an overview

First, we had a series of chapters in individual XML files.

We worked on these files in <oXygen/> XML Editor (we’ll call it oXygen from now on).

These files were referenced in a separate XML file with <xi:include href statements.

In oXygen, there’s a built-in XSL transformer called Saxon (we used Saxon 8B). This transformer takes the XML files, combines them with an XSL stylesheet, and creates a formatting object file.

This formatting object file is then interpreted by a formatting object processor (FOP) to produce, in our case, a PDF. There’s a free FOP from Apache that is included with oXygen. However, this wasn’t robust or feature-rich enough for our needs (even the 0.93 version). So, we had to purchase a FOP called XEP from RenderX.

Note: if you’re going to buy oXygen, buy it through RenderX! There’s a bundle called Docbench that includes the XEP FOP and oXygen that is the same price as either XEP or oXygen on their own. We had already bought oXygen, so ended up paying double when we bought XEP…

XSL introduction and troubleshooting

The biggest technical challenge in this entire process was making a customization layer for Norman Walsh’s XSL stylesheets. XSL stylesheets determine the look of your output (PDF in our case), and the basic stylesheets do a good job. However, not everybody wants their output to look the same! The customization layer is an XSL stylesheet that includes the basic styles and overrides some while introducing others to produce your desired output.

Information about XSL on the web is scattered around the web on messages boards, mailing lists and informational pages, and can be a pain to plow through.

If we are to talk about design in general, if something doesn’t look the way you want it to, this is usually due to one of three factors (see above to review the production process):

A quick tip with troubleshooting: during trial and error with your XSL stuff, turn off the FOP and simply create formatting objects to examine the template syntax. If you don’t know what I’m talking about, you’ll probably get a better idea in the next part about the different style issues.

Our design implementations: XSL and more

And here is the meat of the presentation: the design issues! Here's what we did and how we did it. Note that we used the XEP FOP, so certain style issues are bound to turn out differently if you are using another FOP. I hope to organize this list somewhat if I get the time.

Add a custom font

We have our own company font that is used for all headings in the book.

In XEP.xml (in the folder for the XEP FOP), add this block:

<font-group
xml:base="path/to/folder/with/fontfiles"
label="MaxOT-Book"
embed="true"
subset="false">
<font-family name="MaxOT-Book">
<font>
<font-data ttf="MaxOT-Book.otf"/>
</font>
</font-family>
<font-family name="MaxOT-ExtraBold">
<font>
<font-data ttf="MaxOT-ExtraBold.otf"/>
</font>
</font-family>
<font-alias name="MaxOT-Book" value="MaxOT-Book"/>
</font-group>

Add a hard page break wherever needed

Use this only when absolutely needed, since hard page breaks will change the flow of everything that follows it. Also, if you edit the text above it, you might no longer need a hard page break.

Add this to your customization layer:

<xsl:template match="processing-instruction('custom-pagebreak')">
<fo:block break-before='page'/>
</xsl:template>

Then add this in your XML file whenever you need a hard page break:

<?custom-pagebreak?>

Add a hard line break wherever needed

Apparently there's no DocBook XML equivalent to <br />.

Therefore, you must add this to your customization layer:

<xsl:template match="processing-instruction('custom-linebreak')">
<fo:block />
</xsl:template>

Then add this in your XML file whenever you need a hard line break:

<?custom-linebreak?>

Left-align glossary items

Add this in your customization layer:

<xsl:template match="glossterm" mode="glossary.as.list">
<xsl:variable name="id">
<xsl:call-template name="object.id"/>
</xsl:variable>
<fo:block text-align="left">
<fo:inline id=""><xsl:apply-templates/></fo:inline>
</fo:block>
</xsl:template>

Add a colon to the "See:" and "See also:" glossary items

Find the common/en.xml file in the base XSL stylesheets, then modify this section:

<l:context name="glossary">
<l:template name="see" text="See: %t"/>
<l:template name="seealso" text="See Also: %t"/>
</l:context>

Is there a way to put this in the customization layer? Probably… but I don't know how.

Put the warnings and tips in a gray box

Add the following to your customization layer. Change the first line template match to equal "tip" to add the same styling to tips. Note also the MaxOT-ExtraBold font, which you should change to be your own font:

<xsl:template match="warning">
<xsl:variable name="id">
<xsl:call-template name="object.id"/>
</xsl:variable>
<fo:block keep-together="always" space-before.minimum="0.8em" space-before.optimum="1em"
space-before.maximum="1.2em" border="4pt solid #d0d0d0" padding="4pt" id="">
<fo:block text-align="center">
<fo:inline font-family="MaxOT-ExtraBold" font-size="14pt">
Warning
</fo:inline>
</fo:block>
<fo:block xsl:use-attribute-sets="admonition.properties">
<xsl:apply-templates/>
</fo:block>
</fo:block>
</xsl:template>

Center all figures and their captions

Add this to your customization layer:

<xsl:attribute-set name="figure.properties">
<xsl:attribute name="text-align">center</xsl:attribute>
</xsl:attribute-set>

Make the <parameter> tag monospace instead of the default italic monospace

Add this to your customization layer (you can apply the same format to do other simple tag formatting, like make a guilabel bold:

<xsl:template match="parameter">
<xsl:call-template name="inline.monoseq"/>
</xsl:template>

Chapter title formatting

Here's what we used for titles (probably a handy outline to follow) in the customization layer:

<xsl:template match="title" mode="chapter.titlepage.recto.auto.mode">
<fo:block xsl:use-attribute-sets="chapter.titlepage.recto.style"
margin-top="0in" margin-bottom="0.25in" font-size="25pt">
<xsl:call-template name="component.title">
<xsl:with-param name="node" select="ancestor-or-self::chapter[1]"/>
</xsl:call-template>
</fo:block>
</xsl:template>

Make all section titles a specific font

Add this to your customization layer:

<xsl:param name="title.font.family">Desired-Font-Name</xsl:param>

Keep table row contents together, but allow tables to be split, also table cell padding

Add this to your customization layer:

<xsl:attribute-set name="table.properties"
use-attribute-sets="formal.object.properties">
<xsl:attribute name="keep-together.within-column">auto</xsl:attribute>
</xsl:attribute-set><xsl:attribute-set name="table.cell.padding">
<xsl:attribute name="padding-left">5pt</xsl:attribute>
<xsl:attribute name="padding-right">5pt</xsl:attribute>
<xsl:attribute name="padding-top">5pt</xsl:attribute>
<xsl:attribute name="padding-bottom">5pt</xsl:attribute>
</xsl:attribute-set>

<!– Keep table rows intact. This was originally posted by that XSL guru but
he made a mistake –>

<xsl:param name="keep.row.together">1</xsl:param>

<xsl:template match="row">
<xsl:param name="spans"/>

<fo:table-row>
<xsl:if test=".row.together != '0'">
<xsl:attribute name="keep-together.within-page">always</xsl:attribute>
</xsl:if>
<xsl:call-template name="anchor"/>

<xsl:apply-templates select="(entry|entrytbl)[1]">
<xsl:with-param name="spans" select=""/>
</xsl:apply-templates>
</fo:table-row>

<xsl:if test="following-sibling::row">
<xsl:variable name="nextspans">
<xsl:apply-templates select="(entry|entrytbl)[1]" mode="span">
<xsl:with-param name="spans" select=""/>
</xsl:apply-templates>
</xsl:variable>

<xsl:apply-templates select="following-sibling::row[1]">
<xsl:with-param name="spans" select=""/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>

Vertical placement of table, figure, procedure, and example titles

Add this to your customization layer:

<xsl:param name="formal.title.placement">
figure after
example after
table before
procedure after
</xsl:param>

Styling of miscellaneous titles

Add something like this to your customization layer:

<xsl:attribute-set name="formal.title.properties"
use-attribute-sets="normal.para.spacing">
<xsl:attribute name="font-family">Helvetica</xsl:attribute>
<xsl:attribute name="font-weight">normal</xsl:attribute>
<xsl:attribute name="font-size">10pt</xsl:attribute>
<xsl:attribute name="hyphenate">false</xsl:attribute>
<xsl:attribute name="space-after.minimum">0.4em</xsl:attribute>
<xsl:attribute name="space-after.optimum">0.6em</xsl:attribute>
<xsl:attribute name="space-after.maximum">0.8em</xsl:attribute>
<xsl:attribute name="keep-together.within-column">always</xsl:attribute>
</xsl:attribute-set>

A nice template for running headers and footers

For your customization layer:

<!– Specify the height of the header –>
<xsl:param name="region.before.extent" select="'1in'"/><!– Specify the height of the footer –>
<xsl:param name="region.after.extent" select="'1in'"/><!– Footnote font size as a percentage of the body font. –>
<xsl:param name="footnote.font.size">
<xsl:value-of select=".font.master * 0.8"/>
<xsl:text>pt</xsl:text>
</xsl:param>

<!– Bottom of footer area to bottom of page edge. –>
<xsl:param name="page.margin.bottom">0.5in</xsl:param>

<!– Top of header area to top of main text area. –>
<xsl:param name="body.margin.top">0.5in</xsl:param>

<!– Bottom of main text area to bottom of footer area. –>
<xsl:param name="body.margin.bottom">0.5in</xsl:param>

<xsl:param name="header.column.widths">4 0 1</xsl:param>

<!– Header content and placement. –>
<xsl:template name="header.content">
<xsl:param name="pageclass" select="''"/>
<xsl:param name="sequence" select="''"/>
<xsl:param name="position" select="''"/>
<xsl:param name="gentext-key" select="''"/>
<xsl:variable name="candidate">
<!– sequence can be odd, even, first, blank –>
<!– position can be left, center, right –>
<xsl:choose>

<xsl:when test=" = 'odd' and = 'left'">
<fo:retrieve-marker
retrieve-class-name="section.head.marker" />
</xsl:when>
<xsl:when test=" = 'odd' and = 'center'">
</xsl:when>
<xsl:when test=" = 'odd' and = 'right'">
<fo:page-number/>
</xsl:when>
<xsl:when test=" = 'even' and = 'left'">
<fo:page-number/>

</xsl:when>
<xsl:when test=" = 'even' and = 'center'">
</xsl:when>
<xsl:when test=" = 'even' and = 'right'">
<xsl:apply-templates select="." mode="object.title.markup"/>
</xsl:when>
<xsl:when test=" = 'first' and = 'left'">
</xsl:when>
<xsl:when test=" = 'first' and = 'right'">
</xsl:when>
<xsl:when test=" = 'first' and = 'center'">
</xsl:when>
<xsl:when test=" = 'blank' and = 'left'">
</xsl:when>
<xsl:when test=" = 'blank' and = 'center'">
<!– no output –>
</xsl:when>
<xsl:when test=" = 'blank' and = 'right'">
</xsl:when>
</xsl:choose>
</xsl:variable>

<!– Does runtime parameter turn off blank page headers? –>
<xsl:choose>
<xsl:when test="='blank' and .on.blank.pages=1">
<!– no output –>
</xsl:when>

<!– titlepages have no headers –>
<xsl:when test=" = 'titlepage'">
<!– no output –>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select=""/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<!– Header fonts and font size. –>
<xsl:attribute-set name="header.content.properties">
<xsl:attribute name="font-family">Your-Font</xsl:attribute>
<xsl:attribute name="font-size">10pt</xsl:attribute>
</xsl:attribute-set>

<!– Header rule –>
<xsl:template name="head.sep.rule">
<xsl:param name="sequence" select="''"/>
<xsl:if test=".rule != 0 and != 'first' and !=
'blank'">
<xsl:attribute name="border-bottom-width">1pt</xsl:attribute>
<xsl:attribute name="border-bottom-style">solid</xsl:attribute>
<xsl:attribute name="border-bottom-color">black</xsl:attribute>
</xsl:if>
</xsl:template>

<!– Footer content and placement. –>
<xsl:template name="footer.content">
<xsl:param name="pageclass" select="''"/>
<xsl:param name="sequence" select="''"/>
<xsl:param name="position" select="''"/>
<xsl:param name="gentext-key" select="''"/>
<xsl:variable name="candidate">
<!– sequence can be odd, even, first, blank –>
<!– position can be left, center, right –>
<xsl:choose>
<xsl:when test=" = 'odd' and = 'left'">
</xsl:when>
<xsl:when test=" = 'odd' and = 'center'">
</xsl:when>
<xsl:when test=" = 'odd' and = 'right'">
</xsl:when>
<xsl:when test=" = 'even' and = 'left'">
</xsl:when>
<xsl:when test=" = 'even' and = 'center'">
</xsl:when>
<xsl:when test=" = 'even' and = 'right'">
</xsl:when>
<xsl:when test=" = 'first' and = 'left'">
</xsl:when>
<xsl:when test=" = 'first' and = 'right'">
</xsl:when>
<xsl:when test=" = 'first' and = 'center'">
</xsl:when>
<xsl:when test=" = 'blank' and = 'left'">
</xsl:when>
<xsl:when test=" = 'blank' and = 'center'">
</xsl:when>
<xsl:when test=" = 'blank' and = 'right'">
</xsl:when>
</xsl:choose>
</xsl:variable>
<!– Does runtime parameter turn off blank page footers? –>
<xsl:choose>
<xsl:when test="='blank' and .on.blank.pages=0">
<!– no output –>
</xsl:when>
<!– titlepages have no footers –>
<xsl:when test=" = 'titlepage'">
<!– no output –>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select=""/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<!– Footer fonts and font size. –>
<xsl:attribute-set name="footer.content.properties">
<xsl:attribute name="font-family">Your-Font</xsl:attribute>
<xsl:attribute name="font-size">10pt</xsl:attribute>
</xsl:attribute-set>

<!– Footer rule –>
<xsl:template name="foot.sep.rule">
<xsl:if test=".rule != 0">
<xsl:attribute name="border-bottom-width">0.0pt</xsl:attribute>
<xsl:attribute name="border-bottom-style">solid</xsl:attribute>
<xsl:attribute name="border-bottom-color">black</xsl:attribute>
</xsl:if>
</xsl:template>

Design: what we couldn’t figure out