5.4. Transformation into PDF with XSL-FO

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

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.


5.4.1. XSL-FO Input to the Renderer

Figure 5.5. Outline of the XSL-FO file produced by running Example 5.10 on Example 2.2

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

5.4.2. From the Instance Document to the XSL-FO file

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‑11) with two children:

  • fo:layout-master-set, within the named template define-layoutdefined on line 32‑5, describes the shape of a page with a fo:simple-page-master element (line 34‑6) 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‑7): here a simple repetition of our single page master.

  • fo:page-sequence (line 58‑12) refers to a page-sequence-master in which the content of the page will be given in the fo:flow element (line 60‑13) 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‑8) 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‑10) and the traversal of the instance document starts on line 62‑15 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‑14). 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‑13 of Example 5.10) is composed of fo:list-items one on top of each other. A fo:list-item (line 70‑17) is composed of a fo:list-item-label aligned horizontally with a fo:list-item-body (line 76‑19) 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‑14). The starting position of the list item body must also be specified, most often again with a predefined function fo:body-start (line 76‑19).

The processing of elements starts by creating a new fo:list-item (line 72‑18) with the element name in bold green defined in the attribute set on line 27‑4 as fo:list-item-label (line 70‑17). The fo:list-item-body (line 76‑19) processing depends on whether there are any children elements (or attributes) or not:

  • when there are no children elements (line 78‑20) but possibly text nodes, we display the content of text nodes in a fo:block.

  • when there are children nodes (line 85‑21), they are displayed within a bordered fo:block whose content is a recursively built (line 92‑23) fo:list-block. As we explain below, the current context is also computed and saved in the fo:marker (line 87‑22).

Attributes (line 101‑24) are displayed using the labeledValue named template (line 128‑28): 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‑25 and line 108‑26) 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‑28). This template creates a fo:list-item comprising a fo:list-item-label and a list-item-body. In both cases (line 134‑29 and line 139‑30), 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‑27) 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‑22) 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‑9).

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">                             (1)
            <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">                      (2)
            <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">                    (3)
            <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">                  (4)
            <xsl:attribute name="font-style">italic</xsl:attribute>
            <xsl:attribute name="color">blue</xsl:attribute>
 30     </xsl:attribute-set>
    
        <xsl:template name="define-layout">                              (5)
            <fo:layout-master-set>
                <fo:simple-page-master master-name="a-page" xsl:use-attri(6)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">      (7)
                    <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">                              (8)
 45         <fo:static-content flow-name="xsl-region-before">
                <fo:block text-align-last="justify">
                    <fo:retrieve-marker retrieve-class-name="context"    (9)
                        retrieve-position="first-starting-within-page"/>
                    <fo:leader/>
 50                 Page <fo:page-number/>
                </fo:block>
            </fo:static-content>
        </xsl:template>
        
 55     <xsl:template match="/">                                         (10)
            <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">       (11)
                <xsl:call-template name="define-layout"/>
                <fo:page-sequence master-reference="page-layout">        (12)
                    <xsl:call-template name="define-header"/>
 60                 <fo:flow flow-name="xsl-region-body">                (13)
                        <fo:list-block provisional-distance-between-start(14)s="30mm">
                            <xsl:apply-templates/>                       (15)
                        </fo:list-block>
                    </fo:flow>
 65             </fo:page-sequence>
            </fo:root>
        </xsl:template>                                                  (16)
        
      <xsl:template match="*">
 70     <fo:list-item>                                                   (17)
          <fo:list-item-label end-indent="label-end()">
            <fo:block xsl:use-attribute-sets="element-formatting">       (18)
              <xsl:value-of select="local-name()"/>
            </fo:block>
 75       </fo:list-item-label>
          <fo:list-item-body start-indent="body-start()">                (19)
            <xsl:choose>
              <xsl:when test="count(*)=0 and count(@*)=0">               (20)
                <fo:block>
 80               <fo:inline font-style="normal" color="black">
                    <xsl:value-of select="."/>
                  </fo:inline>
                </fo:block>
              </xsl:when>
 85           <xsl:otherwise>                                            (21)
                  <fo:block xsl:use-attribute-sets="block-decoration">
                    <fo:marker marker-class-name="context">              (22)
                        <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()"/>        (23)
                    </fo:list-block>
                  </fo:block>
 95           </xsl:otherwise>
            </xsl:choose>
          </fo:list-item-body>
        </fo:list-item>
      </xsl:template>
100   
      <xsl:template match="@*">                                          (24)
        <xsl:call-template name="labeledValue">
          <xsl:with-param name="label">
            <fo:inline xsl:use-attribute-sets="attribute-formatting">    (25)
105           @<xsl:value-of select="local-name()"/>
            </fo:inline>
          </xsl:with-param>
          <xsl:with-param name="value">                                  (26)
            <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()">                                      (27)
        <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">                                 (28)
        <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"/>                             (29)
135         </fo:block>
          </fo:list-item-label>
          <fo:list-item-body start-indent="body-start()">
            <fo:block>
              <xsl:copy-of select="$value"/>                             (30)
140         </fo:block>
          </fo:list-item-body>
        </fo:list-item>
      </xsl:template>
    
145 </xsl:stylesheet>
    

1

Attributes that define the global margins of the document.

2

Attributes that define how the box surrounding an element will be displayed.

3

Attributes for the formatting of an element name.

4

Attributes for the formatting of an attribute name.

5

Creates the global layout, here we have the same layout for each page.

6

Defines the layout of a page with margins of 1cm so that they coincide with the global margins.

7

Defines the global layout of pages as the repetition of the same page master.

8

Define the content of the header, it appears in the region-before as the value of the context marker and the page number. The combination of the text-align-last attribute and fo:leader ensures that enough space is added so that the page number appears right-aligned at the right margin of the page.

9

Gets the current value of the marker defined on line 87‑22.

10

Template that matches the root node and that defines the overall shape of the XSL-FO output enclosed in a fo:root.

11

Defines the layout by calling the template defined at line 32‑5 followed by the sequence of pages.

12

The page sequence is filled out according to the definition of pages given on line 38‑7. It first defines the content of the header by calling the named template on line 44‑8 and then gives the content of the document in a single flow.

13

The content of the document to appear in the region-body is composed of a single list-block.

14

Specifies the distance between the start of its item and the start of its body.

15

The content of a fo:list-block is filled by the recursive traversal of all elements of the source document and the application of the appropriate templates depending on the types of elements.

16

Template for an element.

17

Creates an fo:list-item having the name of the element as label. The body of the fo:list-item is the content of the element.

18

Outputs the element name with the formatting defined on line 22‑3.

19

Outputs the content of an element depending on whether it has any children or attributes.

20

When there are no children nodes, the content of text nodes are copied.

21

When there are children nodes or attributes, creates an internal block. First updates the value of the context marker with a string built from the ancestors of this node. This string will be used in the page header (line 44‑8). Then starts a new fo:list-block to be filled by a recursive application of the templates on the content of all nodes.

22

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.

23

Applies the templates on all attributes, element and text nodes of this element.

24

An attribute is output as a one-element list using the named template defined on line 128‑28. Note that the parameter values are complex XSL-FO elements.

25

The label parameter is the name of the attribute preceded by an @ with the appropriate formatting defined on line 27‑4.

26

The value parameter is the value of the attribute (a text node).

27

A non-empty text node is output as a one-element list using the named template defined on line 128‑28 with an empty label and the normalized text content as value parameter.

28

Named template for outputting a single list item with the content of two formal parameters.

29

Because the actual paramater corresponding to label can be a complex XSL-FO element, we need to copy the whole tree of the parameter and not simply use xsl:value-of.

30

Because the actual parameter corresponding to value can be a complex XSL-FO element, we need to copy the whole tree of the parameter and not simply use xsl:value-of.


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.