Welcome to our free Introduction to XSL-FO tutorial. This tutorial is based on Webucator's Introduction to XSL-FO course.
Lesson Goals
We will be creating a PDF that contains children's stories. The result document is LeadersMarkers/Demos/Stories.pdf. Please take a moment to look it over. You should notice the following:
Assume you have an XML document that contains a complete book that is divided into chapters. The table of contents will be created by taking a pass through the entire XML document, grabbing all the chapter titles and outputting those titles with page references. Generally, the title and page reference will be separated by some type of leader. The XSLT document below shows how the table of contents (shown above) was created.
<?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:output method="xml" indent="yes"/> <xsl:template match="/"> <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"> <fo:layout-master-set> <fo:simple-page-master master-name="TOC" page-height="11in" page-width="8.5in" margin=".5in"> <fo:region-body margin=".5in"/> <fo:region-before region-name="TocHead" extent=".5in"/> <fo:region-after region-name="TocFoot" extent=".5in"/> </fo:simple-page-master> ---- C O D E O M I T T E D ---- </fo:layout-master-set> <fo:page-sequence master-reference="TOC" format="i"> <fo:static-content flow-name="TocHead"> <fo:block border-bottom-width="thin" border-bottom-style="solid" border-bottom-color="green" font-weight="bold" text-align="outside"> <xsl:text>Table of Contents</xsl:text> </fo:block> </fo:static-content> <fo:static-content flow-name="TocFoot"> <fo:block border-bottom-width="thin" border-bottom-style="solid" border-bottom-color="green" font-weight="bold" text-align="outside"> <fo:page-number/> </fo:block> </fo:static-content> <fo:flow flow-name="xsl-region-body"> <fo:block text-align="center" font-weight="bold" font-size="larger"> <xsl:text>Table of Contents</xsl:text> </fo:block> <xsl:apply-templates mode="TOC"/> </fo:flow> </fo:page-sequence> <fo:page-sequence master-reference="Stories" initial-page-number="1"> <fo:static-content flow-name="oddHead"> <fo:block border-bottom-width="thin" border-bottom-style="solid" border-bottom-color="green" font-weight="bold" text-align="outside"> <fo:retrieve-marker retrieve-class-name="StoryTitle" retrieve-position="first-including-carryover"/> </fo:block> </fo:static-content> <fo:static-content flow-name="evenHead"> <fo:block border-bottom-width="thin" border-bottom-style="solid" border-bottom-color="green" font-weight="bold" text-align="outside"> <fo:retrieve-marker retrieve-class-name="StoryTitle" retrieve-position="first-including-carryover"/> </fo:block> </fo:static-content> ---- C O D E O M I T T E D ---- </fo:page-sequence> </fo:root> </xsl:template> <xsl:template match="body/div/h2" mode="TOC"> <fo:block font-size="smaller" space-after=".1in" space-before=".15in" text-align-last="justify"> <fo:basic-link internal-destination="{generate-id(.)}"> <xsl:value-of select="."/> <fo:leader leader-pattern="dots"/> <fo:page-number-citation ref-id="{generate-id(.)}"/> </fo:basic-link> </fo:block> </xsl:template> <xsl:template match="body/div/h2" mode="Stories"> <fo:block font-weight="bold" font-size="larger" id="{generate-id(.)}" break-before="odd-page"> <fo:marker marker-class-name="StoryTitle"> <xsl:value-of select="."/> </fo:marker> <xsl:value-of select="."/> </fo:block> </xsl:template> ---- C O D E O M I T T E D ---- </xsl:stylesheet>
In the example above, there is a simple-page-master for the table of contents. There's nothing special about this page master. The page-sequence that references it is also pretty standard, with one exception. It has a format attribute, which specifies how page numbers should be formatted. In this case, it takes the value of "i", meaning lowercase Roman numerals will be used. Other options are "I", "A", "a", and "1" (the default).
In the following page-sequence FO, which contains the actual stories, we set the initial-page-number attribute to "1" to force the page count to start over. This page-sequence will use Arabic notation (the default) for page numbers, because the format attribute is left off.
The page number itself is output with the page-number-citation FO, which uses generate-id() to reference the location at which that node appears in the document.
Leaders are used to separate the story titles from the page numbers. They are created with the <fo:leader> tag as shown below.
To force the page number to be aligned all the way to the right, we set the text-align-last attribute of the block to "justify". For the leader-pattern, we use "dots", which is common for a table of contents. Other options are "rule", "space", and "use-content", which are all shown in the following example.
<?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="Leaders" page-height="11in" page-width="8.5in" margin="1in"> <fo:region-body/> </fo:simple-page-master> </fo:layout-master-set> <fo:page-sequence master-reference="Leaders"> <fo:flow flow-name="xsl-region-body"> <fo:block font-weight="bold" space-before=".5cm" space-after=".5cm">leader-pattern</fo:block> <fo:block space-after=".1in" text-align-last="justify"> A<fo:leader leader-pattern="dots"/>Z </fo:block> <fo:block space-after=".1in" text-align-last="justify"> A<fo:leader leader-pattern="rule"/>Z </fo:block> <fo:block space-after=".1in" text-align-last="justify"> A<fo:leader leader-pattern="space"/>Z </fo:block> <fo:block space-after=".1in" text-align-last="justify"> A<fo:leader leader-pattern="use-content">*</fo:leader>Z </fo:block> <fo:block font-weight="bold" space-before=".5cm" space-after=".5cm">leader-pattern-width</fo:block> <fo:block space-after=".1in" text-align-last="justify"> A<fo:leader leader-pattern="dots" leader-pattern-width="1cm"/>Z </fo:block> <fo:block space-after=".1in" text-align-last="justify"> A<fo:leader leader-pattern="rule" leader-pattern-width="1cm"/>Z </fo:block> <fo:block space-after=".1in" text-align-last="justify"> A<fo:leader leader-pattern="use-content" leader-pattern-width="1cm">*</fo:leader>Z </fo:block> <fo:block font-weight="bold" space-before=".5cm" space-after=".5cm">leader-length</fo:block> <fo:block space-after=".1in" text-align="center"> A<fo:leader leader-pattern="dots" leader-length="50%"/>Z </fo:block> <fo:block space-after=".1in" text-align="center"> A<fo:leader leader-pattern="rule" leader-length="3in"/>Z </fo:block> <fo:block space-after=".1in" text-align="center"> A<fo:leader leader-pattern="space" leader-length="50%"/>Z </fo:block> <fo:block space-after=".1in" text-align="center"> A<fo:leader leader-pattern="use-content" leader-length="3in">*</fo:leader>Z </fo:block> </fo:flow> </fo:page-sequence> </fo:root>
The output is shown below followed by a table showing common leader attributes.
Attribute | Description |
---|---|
leader-pattern |
|
leader-length | the width of the leader |
leader-pattern-width | the distance between each glyph in the leader |
leader-alignment |
|
Markers are used to add information about the current page to static regions. The two FOs used to create and reference markers are marker and retrieve-marker. Markers are grouped by class name with the marker-class-name attribute of the marker FO. This is the only attribute marker takes.
The contents of a marker do not show up in the location of the marker itself. They only appear if the marker is referenced with a retrieve-marker FO.
As shown above, retrieve-marker uses the retrieve-class-name attribute to specify which marker class to reference. It uses the retrieve-position attribute to help identify which marker in that class to use. The retrieve-position attribute takes the following values:
The retrieve-marker FO takes one additional attribute: retrieve-boundary, which is used to specify the domain in which to search for markers. By default, it is limited to the page-sequence, which is almost always what you want; however, you can change it so that it searches the whole document or just the current page.
In this exercise, you will practice creating a nicely formatted table of contents for the song lyrics document we have been working on. You will also use markers so that each page in the document has the appropriate song title in the header. The output is shown in LeadersMarkers/Exercises/Lyrics.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:output method="xml" indent="yes"/> <xsl:template match="/"> <fo:root> ---- C O D E O M I T T E D ---- <fo:page-sequence master-reference="TOC" format="i"> ---- C O D E O M I T T E D ---- <fo:flow flow-name="xsl-region-body"> <fo:block font-family="Helvetica" font-size="22pt" font-weight="bold" text-align="center" space-after=".2in" id="TOC"> Table of Contents </fo:block> <xsl:for-each select="Songs/Song"> <fo:block font-family="Helvetica" font-size="12pt" font-weight="bold" space-after=".1in" text-align-last="justify"> <fo:basic-link internal-destination="{generate-id(Title)}"> <xsl:value-of select="Title"/> <fo:leader leader-pattern="dots"/> <fo:page-number-citation ref-id="{generate-id(Title)}"/> </fo:basic-link> </fo:block> </xsl:for-each> <fo:block id="LastPageToc" break-after="odd-page"/> </fo:flow> </fo:page-sequence> <fo:page-sequence master-reference="Song" initial-page-number="1"> <fo:static-content flow-name="top-right-and-left"> <fo:block font-family="Helvetica" font-size="10pt" text-align="outside" padding-top=".05in" border-top-width=".05in" border-top-color="#0000ff" border-top-style="solid" font-weight="bold"> <fo:retrieve-marker retrieve-class-name="mrkSongTitle" retrieve-position="first-including-carryover"/> </fo:block> </fo:static-content> ---- C O D E O M I T T E D ---- <fo:flow flow-name="xsl-region-body"> <xsl:for-each select="Songs/Song"> <fo:block font-weight="bold" font-size="14pt" font-family="Times" id="{generate-id(Title)}" break-before="page"> <fo:marker marker-class-name="mrkSongTitle"> <xsl:number format="1. "/> <xsl:value-of select="Title"/> </fo:marker> <xsl:number format="1. "/> <xsl:value-of select="Title"/> </fo:block> <fo:block font-size="12pt" font-style="italic" font-family="Times" margin-bottom=".1in" margin-left=".2in"> - <xsl:value-of select="@Artist"/> </fo:block> <fo:block linefeed-treatment="preserve" white-space-collapse="false" wrap-option="wrap" white-space-treatment="preserve" font-size="10pt"> <xsl:value-of select="Lyrics"/> </fo:block> </xsl:for-each> <fo:block id="LastPage"/> </fo:flow> </fo:page-sequence> <!--END PAGE SEQUENCES--> </fo:root> </xsl:template> </xsl:stylesheet>