The previous sections have illustrated the principles of XSLT templates for
producing HTML and character output. XSL also defines a more involved and
powerful formatting tool: XSL-FO, standing for eXtensible Stylesheet
Language-Formatting Object. It is similar in principle to the Cascading
Style Sheets (CSS) (see next section) defined for HTML to separate the information
computation process from the rendering on a specific device (screen, paper, PDA, speech). As
shown in the middle of the flow diagram of Figure 1.3 (page ), Transformations
can produce
Formatting Objects
, i.e. XML elements in the
http://www.w3.org/1999/XSL/Format
namespace with prefix fo
,
which are then rendered on different devices, particularly in PDF.
XSL-FO is a declarative language designed to describe the page content in terms of nested areas, laid out under certain constraints. The main purpose of this approach is the production of printed pages: it allows the definition of the general shape of pages (margins, headers, page numbers, etc.) and the relative placement and nesting of the areas containing the information of the document. Great care has been given to provide a uniform processing of multiple languages and writing systems (not necessarily going from left to right and top to bottom) in the same page. It is also possible to create HTML-like tables and generalized lists as pairs of items with aligned labels and bodies. We will use these lists to illustrate the nesting of XML elements in our PDF output as shown at the bottom of Figure 1.5 and in Figure 5.4.
Figure 5.4 shows the three pages generated by the stylesheet
of Example 5.10 on Example 2.2. An XML element is
displayed with its name in green aligned with its contents. In some
cases, the characters of a label overlap but we could not find a reliable way of adjusting
the position of a list item body depending on the length of its list item label.
We simplify by leaving a distance of 30 mm between the start of the label, given
by the element name, and the start of the indented block describing the element
value. This limitation is understandable because the relative positions of the
label and body must be determined when the fo
elements are generated
by the transformation process but the length of a label is determined
when it is rendered on the PDF page.
Figure 5.4. Three pages of PDF output of compaction by Formatting Objects
![]() |
[compactFO-all.pdf
]: PDF output of compaction by
Formatting Objects (XSL-FO) of Example 2.2. The
output spans over three US Letter format pages that we reduced here to show the
overview of all pages.
The page headers show the context of each page and
the page number.
Figure 5.5. Outline of the XSL-FO file produced by running Example 5.10 on Example 2.2
[compactFO.jpg
]: Outline of the XSL-FO file
produced by running the XSL stylesheet of Example 5.10 on the cellar book example of Example 2.2. This picture was reduced with the
fold/unfold feature in <oXygen/>.
Because the output of an XSL-FO instance document is processed by another program to get the final PDF output, it is a bit difficult to grasp the processing involved in the XSL-FO workflow. So we will go backwards by first looking at the XSL-FO given as input to the renderer. In principle we could write this XML file by hand but, looking from Figure 5.5 which shows only the outline of more than 2500 lines for three PDF pages, we appreciate the fact that it can be produced by a machine... The figure is the output produced by the application of the XSL templates of Example 5.10 on the cellar book instance document (Example 2.2).
An XSL-FO file is an XML file starting with a fo:root
element
with two children elements:
fo:layout-master-set
that describes the
shape of the different types of pages that occur in the
document and the sequence in which they appear.
In Figure 5.5, we have a
simple document so
fo:simple-page-master
(lines 4-8) defines a
single model for all pages with 1 cm margins. Within it, the
header, defined with fo:region-before
will take the
top 1 cm and the real content of the page
will start another 1 cm lower. Here we define only a single type
of master page but for more complex documents it would be
possible to have different master page for title pages, for
first pages, for even or odd pages, etc.
fo:page-sequence-master
(lines 8-11) declares
that the document is an infinite repetition of the above page.
fo:page-sequence
(lines 13-2622) defines the content of the
document that will be rendered according to the page layout we have defined
above. The content of the page is given in the fo:flow
element
(lines 19-2621) which starts in the current page and continues in the
region-body of the next pages. In fact, the only
visible text from Figure 5.5 that appears in Figure 5.4 is the first word
(cellar-book
) produced on line 23. Content that appears at
the same place within each page, such as headers and footers, is called
static-content
(line 14).
We will now look at how to build a stylesheet to produce the XSL-FO file
described in the previous section from an XML instance document. Similarly
to what
we have done to produce HTML output (Section 5.2.3), all tree
structures defined with elements with the fo
namespace prefix
will appear verbatim in the output. XSL-FO elements can be also be created by
xsl:element
templates but we will not need this here.
Example 5.10 starts by defining a group of xsl:attribute-sets
for defining the global formatting parameters of the file. This set up makes it easier to
change the formatting without going into the details of the code.
We must start with a
fo:root
element (line 56‑) with two children:
fo:layout-master-set
, within the named template
define-layout
defined on line 32‑, describes the shape of a page
with a
fo:simple-page-master
element (line 34‑) that defines its margins
relative to the page; within it, we define the
region-body
in
which the content will appear; we also define areas for the header (called
fo:region-before
) and the footer (not used in our example).
Then the sequence of page masters is given (line 38‑): here a simple repetition
of our single page master.
fo:page-sequence
(line 58‑) refers to a
page-sequence-master
in which the content of the page will
be given in the fo:flow
element (line 60‑) which will start in the current page and
continue on the next pages within their region-body.
The content of a header is defined by the named template
define-header
(line 44‑)
which creates a line with the content of the marker on the left and the page
number on the right. As the last (and only) line of the block is to be
justified,
fo:leader
will fill the line with white space in
between.
The overall tree is defined once for the root element of the document
(line 55‑) and the traversal of the
instance document starts on line 62‑
within the
fo:list-block
element in the top-level
fo:flow
element, which corresponds to line 30 of
Figure 5.5.
The nested boxes will be at a distance of 30 mm of each other
(line 61‑). We use the same
type of recursive tree traversal
algorithm as the one for HTML presentation (Example 5.7)
and text compaction (Example 5.9).
Formatting objects create lists as aligned blocks whose relative size and position
must satisfy presentation constraints. As can be seen on line 30 of Figure 5.5, a fo:list-block
(created on line 60‑
of Example 5.10) is composed
of
fo:list-item
s one on top of each other. A fo:list-item
(line 70‑) is composed of a
fo:list-item-label
aligned horizontally with a
fo:list-item-body
(line 76‑)
even if they are not the same height. In our example, the top and left borders of
blocks are colored to show the nesting of blocks which corresponds to the nesting of
elements in the XML file. The starting horizontal position of each
fo:list-item-label
is computed from the value of the enclosing block but
its end position must be specified. Here it is computed by a predefined function
fo:label-end
which takes into account the value specified for the
distance between blocks (line 61‑). The starting
position of the list item body must also be specified, most often again with a
predefined function
fo:body-start
(line 76‑).
The processing of elements starts by creating a new fo:list-item
(line 72‑) with the element name in bold green
defined in the attribute set on line 27‑
as
fo:list-item-label
(line 70‑).
The
fo:list-item-body
(line 76‑) processing
depends on whether there are any children elements (or attributes) or not:
when there are no children elements (line 78‑) but possibly text nodes,
we display the content of text nodes in a
fo:block
.
when there are children nodes (line 85‑), they are displayed within a bordered
fo:block
whose content is a recursively built (line 92‑)
fo:list-block
. As
we explain below, the current context is also computed and saved in the
fo:marker
(line 87‑).
Attributes (line 101‑) are displayed using the
labeledValue
named template
(line 128‑): their name is in blue italicized text (as
the
fo:list-item-label
) and their text content as
fo:list-item-body
. Note here the use of a tree fragment value as actual
parameters (line 104‑ and
line 108‑
) to the
labeledValue
named template. fo:inline
elements create ordinary
text. The area for display is created with a fo:block
element in the
labeledValue
named template (line 128‑). This template creates a
fo:list-item
comprising a fo:list-item-label
and a
list-item-body
. In both cases (line 134‑ and line 139‑
), we insert the tree given as parameter by
means of
xsl:copy-of
and not the usual
xsl:value-of
, which would only return the content of
the tree given as parameter and not the tree value itself.
Text nodes (line 116‑) are output as a list item with an
empty label i.e. an empty in-line block, again using the
labeledValue
named template, and the text content as body for the list
item. Text nodes comprizing only spaces and newlines (whose normalization gives an
empty string) are ignored.
Because the output of this program is longer than a single page (see Figure 5.4), then it flows on the following
one. But it is interesting to show the current context of the start of the page in its
header. This is done by creating a fo:marker
(line 87‑) at each new nested block containing the names
of the elements that are the ancestors of the current node. The current value of
a marker at the start of the page will appear in the left part of the header (line 47‑
).
Example 5.10. [compactFO.xsl
] Stylesheet to transform the
information of the cellar book (Example 2.2) into the
colored nested blocks representation of Figure 5.4
1 <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:fo="http://www.w3.org/1999/XSL/Format" version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 5 <xsl:attribute-set name="page-size"><xsl:attribute name="margin-top">1cm</xsl:attribute> <xsl:attribute name="margin-left">1cm</xsl:attribute> <xsl:attribute name="margin-right">1cm</xsl:attribute> <xsl:attribute name="margin-bottom">1cm</xsl:attribute> 10 </xsl:attribute-set> <xsl:attribute-set name="block-decoration">
<xsl:attribute name="border-color">black</xsl:attribute> <xsl:attribute name="border-left-style">solid</xsl:attribute> 15 <xsl:attribute name="border-left-width">thin</xsl:attribute> <xsl:attribute name="border-top-style">solid</xsl:attribute> <xsl:attribute name="border-top-width">thin</xsl:attribute> <xsl:attribute name="padding-left">2mm</xsl:attribute> <xsl:attribute name="space-after">1mm</xsl:attribute> 20 </xsl:attribute-set> <xsl:attribute-set name="element-formatting">
<xsl:attribute name="font-weight">bold</xsl:attribute> <xsl:attribute name="color">green</xsl:attribute> 25 </xsl:attribute-set> <xsl:attribute-set name="attribute-formatting">
<xsl:attribute name="font-style">italic</xsl:attribute> <xsl:attribute name="color">blue</xsl:attribute> 30 </xsl:attribute-set> <xsl:template name="define-layout">
<fo:layout-master-set> <fo:simple-page-master master-name="a-page" xsl:use-attri
bute-sets="page-size"> 35 <fo:region-body xsl:use-attribute-sets="page-size" margin-left="0cm" margin-right="0cm"/> <fo:region-before extent="1cm"/> </fo:simple-page-master> <fo:page-sequence-master master-name="page-layout">
<fo:repeatable-page-master-reference master-reference="a-page"/> 40 </fo:page-sequence-master> </fo:layout-master-set> </xsl:template> <xsl:template name="define-header">
45 <fo:static-content flow-name="xsl-region-before"> <fo:block text-align-last="justify"> <fo:retrieve-marker retrieve-class-name="context"
retrieve-position="first-starting-within-page"/> <fo:leader/> 50 Page <fo:page-number/> </fo:block> </fo:static-content> </xsl:template> 55 <xsl:template match="/">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:call-template name="define-layout"/> <fo:page-sequence master-reference="page-layout">
<xsl:call-template name="define-header"/> 60 <fo:flow flow-name="xsl-region-body">
<fo:list-block provisional-distance-between-start
s="30mm"> <xsl:apply-templates/>
</fo:list-block> </fo:flow> 65 </fo:page-sequence> </fo:root> </xsl:template>
<xsl:template match="*"> 70 <fo:list-item>
<fo:list-item-label end-indent="label-end()"> <fo:block xsl:use-attribute-sets="element-formatting">
<xsl:value-of select="local-name()"/> </fo:block> 75 </fo:list-item-label> <fo:list-item-body start-indent="body-start()">
<xsl:choose> <xsl:when test="count(*)=0 and count(@*)=0">
<fo:block> 80 <fo:inline font-style="normal" color="black"> <xsl:value-of select="."/> </fo:inline> </fo:block> </xsl:when> 85 <xsl:otherwise>
<fo:block xsl:use-attribute-sets="block-decoration"> <fo:marker marker-class-name="context">
<xsl:value-of select="string-join(for $n in ancestor::* return local-name($n),' | ')"/> 90 </fo:marker> <fo:list-block> <xsl:apply-templates select="@*|*|text()"/>
</fo:list-block> </fo:block> 95 </xsl:otherwise> </xsl:choose> </fo:list-item-body> </fo:list-item> </xsl:template> 100 <xsl:template match="@*">
<xsl:call-template name="labeledValue"> <xsl:with-param name="label"> <fo:inline xsl:use-attribute-sets="attribute-formatting">
105 @<xsl:value-of select="local-name()"/> </fo:inline> </xsl:with-param> <xsl:with-param name="value">
<fo:inline font-style="normal" color="black"> 110 <xsl:value-of select="."/> </fo:inline> </xsl:with-param> </xsl:call-template> </xsl:template> 115 <xsl:template match="text()">
<xsl:variable name="content" select="normalize-space(.)"/> <xsl:if test="string-length($content)>0"> <xsl:call-template name="labeledValue"> 120 <xsl:with-param name="label"> <fo:inline/> </xsl:with-param> <xsl:with-param name="value" select="$content"/> </xsl:call-template> 125 </xsl:if> </xsl:template> <xsl:template name="labeledValue">
<xsl:param name="label"/> 130 <xsl:param name="value"/> <fo:list-item> <fo:list-item-label end-indent="label-end()"> <fo:block> <xsl:copy-of select="$label"/>
135 </fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()"> <fo:block> <xsl:copy-of select="$value"/>
140 </fo:block> </fo:list-item-body> </fo:list-item> </xsl:template> 145 </xsl:stylesheet>
|
Attributes that define the global margins of the document. |
|
Attributes that define how the box surrounding an element will be displayed. |
|
Attributes for the formatting of an element name. |
|
Attributes for the formatting of an attribute name. |
|
Creates the global layout, here we have the same layout for each page. |
|
Defines the layout of a page with margins of 1cm so that they coincide with the global margins. |
|
Defines the global layout of pages as the repetition of the same page master. |
|
Define the content of the header, it appears in the
|
|
Gets the current value of the marker defined on
line 87‑ |
|
Template that matches the root node and that defines the overall shape
of the XSL-FO output enclosed in a |
|
Defines the layout by calling the template defined at
line 32‑ |
|
The page sequence is filled out according to the definition of pages given on
line 38‑ |
|
The content of the document to appear in the |
|
Specifies the distance between the start of its item and the start of its body. |
|
The content of a |
|
Template for an element. |
|
Creates an |
|
Outputs the element name with the formatting defined on line 22‑ |
|
Outputs the content of an element depending on whether it has any children or attributes. |
|
When there are no children nodes, the content of text nodes are copied. |
|
When there are children nodes or attributes, creates an internal block.
First updates the value of the |
|
Creates the content of the marker with an XPath expression that loops over all ancestors of the current node and creates a list with the names of their elements. The strings in this list are then concatenated separated with a vertical bar. |
|
Applies the templates on all attributes, element and text nodes of this element. |
|
An attribute is output as a one-element list using the named template defined on
line 128‑ |
|
The |
|
The |
|
A non-empty text node is output as a one-element list using the named template defined on
line 128‑ |
|
Named template for outputting a single list item with the content of two formal parameters. |
|
Because the actual paramater corresponding to |
|
Because the actual parameter corresponding to |
This section has shown how to produce publication quality output from an XML file. We have used nested list blocks to align the name of an element with its content, but nested tables could also have been used. This would allow the horizontal centering of the element name with respect to its content. The principles remain the same but the code would be a bit longer because tables have more options.