facebook google plus twitter
Webucator's Free Introduction to XSL-FO Tutorial

Lesson: Leaders, Markers and Page Numbers

Welcome to our free Introduction to XSL-FO tutorial. This tutorial is based on Webucator's Introduction to XSL-FO course.

Lesson Goals

  • To create a well-designed table of contents.
  • To work with leaders.
  • To work with markers.
  • To output and reference page numbers and specify page number formats.

The Output

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:

  1. The document has a nicely-formatted table of contents (shown below).This table of contents is made up of the headings from the source XHTML document: LeadersMarkers/Demos/Stories.html. The page numbers are dynamically determined.
  2. The page numbering in the footer of the table of contents (not shown above) is in Roman numerals.
  3. After the table of contents, the page numbers start over again from 1 and use Arabic numbers.
  4. Each story begins on an odd page.
  5. The titles of the stories are shown in the headers.

Table of Contents

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.

Code Sample:

LeadersMarkers/Demos/Stories.xsl
<?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>

Page Numbering

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.

page-number-citation

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.

<fo:page-number-citation ref-id="{generate-id(.)}"/>

Leaders

Leaders are used to separate the story titles from the page numbers. They are created with the <fo:leader> tag as shown below.

<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>

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.

Code Sample:

LeadersMarkers/Demos/Leaders.fo
<?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.

<fo:leader> Attributes
Attribute Description
leader-pattern
  • dots
  • rule
  • space
  • use-content
leader-length the width of the leader
leader-pattern-width the distance between each glyph in the leader
leader-alignment
  • none
  • reference-area
  • page

Markers

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.

<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>

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.

<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>

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:

  • first-starting-within-page - references the first marker on the current page.
  • first-including-carryover - references the first marker on the current page. If there are no markers on the current page, it references the last marker before the current page.
  • last-starting-within-page - references the last marker on the current page.
  • last-ending-within-page - references the last marker on the current page that is contained in a FO whose full content is also on the current page.

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.

Using Leaders and Markers

Duration: 15 to 20 minutes.

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.

  1. Open LeadersMarkers/Exercises/Lyrics.xsl for editing.
  2. Add code to transform Lyrics.xml to create a FO that will render a PDF like LeadersMarkers/Exercises/Lyrics.pdf.
    • The table of contents should be nicely formatted.
    • Each page in the main document (e.g, after the table of contents), should show the name of the current story in the header.
  3. To test your solution, transform LeadersMarkers/Exercises/Lyrics.xml against LeadersMarkers/Exercises/Lyrics.xsl.

Solution:

LeadersMarkers/Solutions/Lyrics.xsl
<?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>