In the following examples you need an XML-FO processor to produce formatted output from your XMLFO documents. All the examples in this section are presented using the Apache processor that can be downloaded from http://www.xml.apache.org/fop/. This is an open-source project that processes the XSL-FO document you supply to the designated output. It is a command line utility and its syntax is discussed shortly.
Also, with a Microsoft Windows operating system you may need a Microsoft Java Virtual Machine. If you do not know whether the Java VM is installed, run or install software diagnostics such as Belarc Advisor (http://www.belarc.com) to audit the software installed on your machine. If you do not have the Java VM, you can download and install the service. Because Microsoft no longer supports the Java VM, various third party sites now provide the download:
q http://www.techtips4u.com/downloads/#MSJavx86
q http://www.download.windowsupdate.com/msdownload/update/v3-19990518/cabpool/MSJavWU_8073687b82d41db93f4c2a04af2b34d.exe
In order to make the examples a little easier, you can download the XMLSpy suite of applications on a free 30 day trial from http://www.altova.com. This provides an XML, XSL, XSLT editor and also a convenient download as a plug in which will install the Apache FOP and configure XMLSpy to use it. This enables you to create a project with an XML document source, XSLT to transform the data into an XSL-FO document and instruct XMLSpy to process the resulting XSL-FO document into PDF. All the subsequent examples in this chapter will use this method and therefore is the recommended method (You still need the Java VM for Microsoft platforms). In scenarios when you wish to see the results of your XSL-FO document in another format, you can use the Apache FOP directly through the command line.
In order to view the examples later in the chapter, you need Adobe Acrobat Reader as this will be the chosen output for the examples.
The following code shows a sample XSL-FO doc for the Hello World example.
<?xml version="1.0" encoding="ISO-8859-1"?> <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"> <fo:layout-master-set> <fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin-top="1.0cm" margin-bottom="1.0cm" margin-left="1.0cm" margin-right="1.0cm"> <fo:region-body margin="1.0cm"/> <fo:region-before extent="1.0cm"/> <fo:region-after extent="1.0cm"/> </fo:simple-page-master> </fo:layout-master-set> <fo:page-sequence master-reference="A4"> <fo:flow flow-name="xsl-region-body"> <fo:block>Hello World</fo:block> </fo:flow> </fo:page-sequence> </fo:root>
Using the command prompt, run the XSL-FO code through the Apache Formatting Objects Processor (FOP). Your command line resembles the following:
fop hello.xml -txt hello.txt
Run through the Apache Formatting Objects Processor (FOP), the output for a text file looks like the screen in Figure 4-6.
Figure 4-6
If you change the output to a PDF file, as follows:
fop hello.xml -pdf hello.pdf
The output looks like what you see in Figure 4-7.
Figure 4-7
Not exactly earth-shattering, but this example enables you to try out using the Apache FOP and is a starting point for looking at some of the other features available within XSL-FO.
Note that the fo:flow element in the source XML-FO. The fo:flow element is the root level element for all subsequent content. The fo:flow does not create an area, areas are created for all the child elements contained within the open and closing tags in the order in which they appear. In our example, the child element is an fo:block containing the Hello World text.
Within the fo:flow the attribute flow-name specifies where in the page the flow will be rendered. The allowed values correspond to the regions discussed earlier in the list of possible values:
q xsl-region-body-Body
q xsl-region-before-Header
q xsl-region-after-Footer
q xsl-region-start-Left
q xsl-region-end-Right
The fo:block element usually host paragraphs, tables captions and so on. Formatting is applied to all elements within the block unless the child elements override the formatting. This is a form of inheritance within the XSL-FO model whereby the child elements inherit the formatting properties of their parents or they can override the settings by specifying their own property values.
To apply a font to the text you add the following:
<fo:flow flow-name="xsl-region-body"> <fo:block font-size="20pt" font-weight="bold" font-family="verdana">Hello World </fo:block> </fo:flow>
Begin by putting together a slightly better example than our Hello World application. Consider the following XML listing. This is a set of postal codes and that demonstrate some of the basic formatting.
<?xml version="1.0" encoding="UTF-8"?> <ROOT> <PostCode> <PostCode>G1</PostCode> <City>Glasgow</City> </PostCode> <PostCode> <PostCode>EH1</PostCode> <City>Edinburgh</City> </PostCode> <PostCode> <PostCode>PA1</PostCode> <City>Paisley</City> </PostCode> <PostCode> <PostCode>NE5</PostCode> <City>Newcastle</City> </PostCode> </ROOT>
You can generate a basic list of all the postcodes and cities they relate to. The following XSLT produces an XML-FO to list each of the postcode elements.
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"> <xsl:template match="/"> <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"> <fo:layout-master-set> <fo:simple-page-master master-name="A4" page-width="29.7cm" page-height="21cm" margin-top="1cm" margin-bottom="1cm" margin-left="1cm" margin-right="1cm"> <fo:region-body/> </fo:simple-page-master> </fo:layout-master-set> <fo:page-sequence master-reference="A4"> <fo:flow flow-name="xsl-region-body"> <xsl:apply-templates select="//ROOT/PostCode"/> </fo:flow> </fo:page-sequence> </fo:root> </xsl:template> <xsl:template match="//ROOT/PostCode"> <fo:block><xsl:value-of select="PostCode"/>-<xsl:value-of select="City"/></fo:block> </xsl:template> </xsl:stylesheet>
Using XMLSpy, you can apply the transformation to the XML and view the results in any of the following formats:
q PDF-Default
q Text-Standard textual representation of results
q XML-An XML area tree
q MIF-Maker Interchange Format
q PCL-Printer Control Language
q Postscript-Adobe postscript format
When you take the default PDF, the results look like the screen in Figure 4-8.
Figure 4-8
You can now change the transformation to produce XSL-FO that renders the XML using a bulleted list. The fo:list-block element contains fo:list-item elements which in turn must contain a fo:listitem: label and one or more fo:list-item-body elements. The fo-list-item is optional because it is implicit within the fo:list-block. The fo:list-item-label allows us to insert a bullet character or as we shall see shortly, a bullet image. The fo:list-body, contains the list items themselves.
Within either the fo:list-block or fo:list-body, you can specify any indents we wish to apply. The attributes start-indent and end-indent allow us to specify the indent in millimeters, centimeters, etc. There is also a special indent function body-start() that sets the start-indent to value calculated from provisional-distance-between-starts. The provisional-distance-between-starts can be set to a value at the fo-list-block element.
<fo:page-sequence master-reference="A4"> <fo:flow flow-name="xsl-region-body"> <fo:list-block provisional-distance-between-starts="2.0cm"> <xsl:apply-templates select="//ROOT/PostCode"/> </fo:list-block> </fo:flow> </fo:page-sequence> </fo:root> </xsl:template> <xsl:template match="//ROOT/PostCode"> <fo:list-item> <fo:list-item-label start-indent="1.0cm" end-indent="1.0cm"> <fo:block > <fo:external-graphic src="/books/2/381/1/html/2/C:\Wrox\Professional XML\Chp4\Code\POSTITL.jpg" content-height="0.5cm"/> </fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()" > <fo:block > <xsl:value-of select="PostCode"/>-<xsl:value-of select="City"/> </fo:block> </fo:list-item-body> </fo:list-item> </xsl:template>
This code produces the following output shown in Figure 4-9.
Figure 4-9
You can also use images as the bullet points or include them in the list (for example, a traffic light indicator or progress bar for a task list).
<xsl:template match="//ROOT/PostCode"> <fo:list-item> <fo:list-item-label start-indent="1.0cm" end-indent="1.0cm"> <fo:block > <fo:external-graphic src="/books/2/381/1/html/2/C:\Wrox\Professional XML\Chp4\Code\POSTITL.jpg" content-height="0.5cm"/> </fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()" > <fo:block > <xsl:value-of select="PostCode"/>-<xsl:value-of select="City"/> </fo:block> </fo:list-item-body> </fo:list-item> </xsl:template>
The output is shown in Figure 4-10.
Figure 4-10
You can put this detail into a neatly formatted table. First, look at the HTML table with XSL-FO elements and attribute equivalents:
HTML | XSL-FO Element | Description/Values |
---|---|---|
<TABLE> | fo:table-and-caption | Table and optional caption |
<TH> | fo:table-header | Table header |
<TR> | fo:table-row | Table row |
<TD> | fo:table-cell | Table cell |
<COLUMN> | fo-table-column | Table column |
<TBODY> | fo:table-body | Table body |
<TFOOT> | fo-table-footer | Table footer |
<COLSPAN> | number-columns-spanned | number of columns to span on table-cell element |
<ROWSPAN> | number of rows to span | number of rows to span in table-row element |
NA | empty-cells | show or hide empty cells in table-default is show |
NA | table-omit-header-at-break | if table spans multiple pages false (default) displays the header on each page; true does not. |
NA | table-omit-footer-at-break | If table spans multiple pages false (default) displays the header on each page; true does not. |
<TBODY> | fo:table-body | Table body |
<TFOOT> | fo-table-footer | Table footer |
<CELLPADDING> | padding-left, padding-right, padding-top, padding-bottom. | Specifies the padding width for cells. |
<CELLSPACING> | NA | |
Width | column-width | Specifies the width of the table column. |
This code shows an example of the table structure within an XSLT that produces the XSL-FO document for sample postal codes.
<xsl:template match="/"> <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"> <fo:layout-master-set> <fo:simple-page-master master-name="A4" page-width="29.7cm" page-height="21cm" margin-top="1cm" margin-bottom="1cm" margin-left="1cm" margin-right="1cm"> <fo:region-body/> </fo:simple-page-master> </fo:layout-master-set> <fo:page-sequence master-reference="A4"> <fo:flow flow-name="xsl-region-body"> <fo:table> <fo:table-column column-width="3cm"/> <fo:table-column column-width="3cm"/> <fo:table-body> <fo:table-row background-color="silver"> <fo:table-cell border-style="solid" padding- top="2px" padding-bottom="2px" padding-left="2px" padding-right="2px"> fo:block>Postcode</fo:block> </fo:table-cell> <fo:table-cell border-style="solid" padding- top="2px" padding-bottom="2px" padding-left="2px" padding-right="2px"> <fo:block>City</fo:block> </fo:table-cell> </fo:table-row> <xsl:apply-templates select="//ROOT/PostCode"> <xsl:sort data-type="text" select="City"/> </xsl:apply-templates> </fo:table-body> </fo:table> </fo:flow> </fo:page-sequence> </fo:root> </xsl:template> <xsl:template match="//ROOT/PostCode"> <fo:table-row> <fo:table-cell border-style="solid" padding-top="2px" padding-bottom="2px" padding-left="2px" padding-right="2px"> <fo:block><xsl:value-of select="PostCode"/></fo:block> </fo:table-cell> <fo:table-cell border-style="solid" padding-top="2px" padding-bottom="2px" padding-left="2px" padding-right="2px"> <fo:block><xsl:value-of select="City"/></fo:block> </fo:table-cell> </fo:table-row> </xsl:template>
The results of this transformation in PDF format are shown in Figure 4-11.
Figure 4-11
You now work through a real-world example to gain a more thorough understanding of the mechanics of the XSL-FO process. The example produces a printable invoice from a given XML set that is formatted and paginated appropriately and available to a consumer (a customer-facing Web site, for example) in PDF format.
A subset of the source XML for the example is shown in the following code (the full file can be downloaded from the companion Web site for this book at http://www.wrox.com):
Important | It should be noted that depending on the software currently running on your computer, you may receive some warnings while processing and may receive slightly different output. This may also be the case if you are targeting a different browser (when rendering HTML). If you have to ensure the content is presented identically for every target browser, you should test on each target, make changes to the XSL-FO for that target, then your page should use client-side script to check which target you are running on and produce the content accordingly. |
<?xml version="1.0" encoding="UTF-8"?> <ROOT DATE="19/06/2006" Time="10:41:09"> <Invoice Invoice> <InvoiceID>4561598</InvoiceID> <ContractID>CH20721 </ContractID> <AccountNumber>002585</AccountNumber> <CustomerName>Smiths Construction</CustomerName> <CustomerAddress1>123 Glassford St</CustomerAddress1> <CustomerAddress2>Glasgow</CustomerAddress2> <CustomerAddress3>Lanarkshire</CustomerAddress3> <CustomerAddress4>Scotland</CustomerAddress4> <CustomerPostCode>G2 4YR</CustomerPostCode> <DeliveryAddress1>Carlisle Railway Station</DeliveryAddress1> <DeliveryAddress2>HARKER</DeliveryAddress2> <DeliveryAddress3>CARLISLE</DeliveryAddress3> <OrderNumber>TC258567</OrderNumber> <OrderName>MICK</OrderName> <InvoiceDate>30/06/06</InvoiceDate> <HireStatus>HIRE COMPLETE</HireStatus> <PreVATTotal>166.28</PreVATTotal> <VAT1>29.10</VAT1> <InvoiceTotal>195.38</InvoiceTotal> <CreditTerms>30</CreditTerms> <HireItem> <ProductCode>CHAA</ProductCode> <Description>JUNCTION BOX </Description> <FromDate>01/06/06</FromDate> <ToDate>21/06/06</ToDate> <Weeks>3.00</Weeks> <Rate>10.75</Rate> <Quantity>1</Quantity> <Discount>50.00</Discount> <VATCode>1</VATCode> <Value>16.14</Value> </HireItem> <HireItem> <ProductCode>CHXL 03206</ProductCode> <Description>EXT LEAD </Description> <FromDate>01/06/06</FromDate> <ToDate>30/06/06</ToDate> <Weeks>4.40</Weeks> <Rate>1.25</Rate> <Quantity>1</Quantity> <VATCode>1</VATCode> <Value>5.50</Value> </HireItem> <HireItem> <ProductCode>CHXL 01917</ProductCode> <Description>EXT LEAD </Description> <FromDate>01/06/06</FromDate> <ToDate>30/06/06</ToDate> <Weeks>4.40</Weeks> <Rate>1.25</Rate> <Quantity>1</Quantity> <VATCode>1</VATCode> <Value>5.50</Value> </HireItem> <SaleItem> <Description>STARTER KEY</Description> <Date>01/06/06</Date> <Quantity>1</Quantity> <Price>2.10</Price> <VATCode>1</VATCode> <Value>-2.10</Value> </SaleItem> <SaleItem> <Description>DRILL CHUCK KEY</Description> <Date>01/06/06</Date> <Quantity>1</Quantity> <Price>1.75</Price> <VATCode>1</VATCode> <Value>-1.75</Value> </SaleItem> <SaleItem> <Description>ALLEN KEY</Description> <Date>01/06/06</Date> <Quantity>1</Quantity> <Price>3.10</Price> <VATCode>1</VATCode> <Value>-3.10</Value> </SaleItem> <SaleItem> <Description>MISC</Description> <Date>01/06/06</Date> <Quantity>1</Quantity> <Price>3.10</Price> <VATCode>1</VATCode> <Value>-3.10</Value> </SaleItem> </Invoice> </ROOT>
The preceding XML represents an invoice produced for a customer who is being charged for hiring equipment from the providing company. The invoice is encapsulated within the <Invoice> tag and has child elements <HireItem> showing the specific invoicing details for every individual piece of equipment that has been hired by the customer.
The <invoice> section contains all the top level detail for the invoice, such as invoice number, customer details and summary values. The <HireItem> section contains the specifics about the equipment such as descriptions and rates charged. The <SaleItem> section contains a record of sales purchased by the customer.
The invoice you want to produce shows the invoice header information at the top of the invoice and provides a detailed breakdown of the equipment hired in a list following the header data.
Figure 4-12 shows a high-level layout for the invoice.
Figure 4-12
The header section has one table that holds the company logo and all the top level invoice details. You create a table and populate it using an apply-templates section for the ROOT/Invoice element.
The initial setup of the invoice, an A4–landscape oriented format is shown in the following code:
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"> <xsl:template match="/"> <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"> <fo:layout-master-set> <fo:simple-page-master master-name="A4" page-width="29.7cm" page-height="21cm" margin-top="1cm" margin-bottom="1cm" margin-left="1cm" margin-right="1cm"> <fo:region-body/> </fo:simple-page-master> </fo:layout-master-set> <fo:page-sequence master-reference="A4">
The XSLT code creates the table structure for the header data as shown in the following code:
<fo:flow flow-name="xsl-region-body"> <fo:table> <fo:table-column column-width="9cm"/> <fo:table-column column-width="9cm"/> <fo:table-column column-width="9cm"/> <fo:table-body> <xsl:apply-templates select="//ROOT/Invoice"/> </fo:table-body> </fo:table>
The header comprises three columns. The first holds invoice and contract information, the second customer information and the third delivery details. The apply-templates section builds up the table by creating and formatting the cells with relevant data from the source XML.
<xsl:template match="//ROOT/Invoice"> <fo:table-row background-color="silver" text-align="center" width="27cm"> <fo:table-cell number-columns-spanned="3"> <fo:block> <fo:external-graphic src="/books/2/381/1/html/2/C:\Wrox\Professional XML\Chp4\Code\AcmeLogo.JPG" content-height="0.5cm"/> </fo:block> </fo:table-cell> </fo:table-row> <fo:table-row background-color="white" width="27cm"> <fo:table-cell number-columns-spanned="3" border-style="none"> <fo:block font-family="serif" font-weight="bold" font-size="28pt"> Hire Invoice </fo:block> </fo:table-cell> </fo:table-row> <fo:table-row background-color="white" width="100%"> <fo:table-cell padding="2px" border-top-style="solid" border-left-style="solid" border-right-style="solid" width="33%"> <fo:block> Invoice: <xsl:value-of select="InvoiceID"/> </fo:block> </fo:table-cell> <fo:table-cell padding="2px" border-top-style="solid" border-left-style="solid" border-right-style="solid" width="33%"> <fo:block font-family="serif" font-weight="bold" font-size="14pt"> Customer Details </fo:block> </fo:table-cell> <fo:table-cell padding="2px" border-top-style="solid" border-left-style="solid" border-right-style="solid" width="33%" text-align="right"> <fo:block font-family="serif" font-weight="bold" font-size="14pt"> Site Address </fo:block> </fo:table-cell> </fo:table-row> <fo:table-row background-color="white" width="100%"> <fo:table-cell padding="2px" border-left-style="solid" border-right-style="solid" width="33%"> <fo:block> Contract: <xsl:value-of select="ContractID"/> </fo:block> </fo:table-cell> <fo:table-cell padding="2px" border-left-style="solid" border-right-style="solid" width="33%"> <fo:block> <xsl:value-of select="AccountNumber"/> </fo:block> </fo:table-cell> <fo:table-cell padding="2px" border-left-style="solid" border-right-style="solid" width="33%" text-align="right"> <fo:block> <xsl:value-of select="DeliveryAddress1"/> </fo:block> </fo:table-cell> </fo:table-row> </xsl:template>
The preceding code shows the first three rows of the header table. The subsequent header rows are identical to the last two rows with the exception of content.
Some new attributes are shown in the example that must be explained (these are bolded in the code). The first is the attribute number-columns-spanned. This enables you to create the HTML equivalent of COLSPAN on the table. For the logo and the header label you want the logo to span the three columns and be centered. You want the header to span the three columns and be left-aligned.
The second attribute is text-align. This attribute simply allows you to specify the alignment of the child content as left, right, or center.
The padding attribute sets the cell padding area that outlines the content. This can be broken down specifically using padding-top, padding-bottom, padding-left and padding-right. You can control cell padding for each dimension of the cell.
You can also applied various font attributes to some of the fo:block elements in the code. Some of the commonly used font attributes are shown in the following list:
q Font-family-Serif, sans-serif, fantasy etc
q Font-size-Can be specified in points, relatively (larger, smaller) or using constants (small, medium, large, x-large etc)
q Font-style-Normal, italic, oblique, backslant or inherit
q Font-weight-Constants (normal, bold, bolder or lighter) or an integer value representing the weighting.
Several attributes enable you to format the border of table cells. The border style is broken down into border-top-style, border-bottom-style, border-left-style and border-right-style; these can have the following values:
q None-No border
q Solid-Single pixel border
q Dotted-Single full stop broken border
q Dashed-Short line broken border
q Hidden-Same as none except when conflict occurs with borders for table elements
q Double-Double lined solid border. 1 pixel line, 1 pixel space, 1 pixel line
q Inset-Embedded cell style
q Outset-Raised cell style
q Groove-Embedded border style
q Ridge-Raised border style
If you create the subsequent rows for the header table and apply the transformation you achieve the results shown in Figure 4-13.
Figure 4-13
Important | It should be noted that the presentation of the results on screen (in, for example, Adobe Acrobat) are not always exactly what will be printed. It is wise to periodically print to your target printer in order to check the results. |
You now wish to show the payment terms on the invoice for the customer. This is just an fo:block element neatly formatted and presented under the header table.
<fo:block padding="2px"> Payment Terms: This invoice must be paid no later than <xsl:value-of select="//ROOT/Invoice/CreditTerms"/> from the invoice date. </fo:block>
Next you want to render the series of hire item records in a table. First, you build up the table structure and the header record. The XSLT code that follows shows how to achieve this based on the XML data shown previously.
<fo:table padding="2px"> <fo:table-column column-width="4cm"/> <fo:table-column column-width="5cm"/> <fo:table-column column-width="3cm"/> <fo:table-column column-width="3cm"/> <fo:table-column column-width="2cm"/> <fo:table-column column-width="2cm"/> <fo:table-column column-width="2cm"/> <fo:table-column column-width="2cm"/> <fo:table-column column-width="2cm"/> <fo:table-column column-width="2cm"/> <fo:table-header border-style="solid"> <fo:table-row background-color="white"> <fo:table-cell number-columns-spanned="10"> <fo:block font-family="serif" font-weight="bold" font-size="20pt">Hire Items</fo:block> </fo:table-cell> </fo:table-row> <fo:table-row background-color="silver"> <fo:table-cell padding="2px"> <fo:block>Code</fo:block> </fo:table-cell> <fo:table-cell padding="2px"> <fo:block>Description</fo:block> </fo:table-cell> <fo:table-cell padding="2px"> <fo:block>From Date</fo:block> </fo:table-cell> <fo:table-cell padding="2px"> <fo:block>To Date</fo:block> </fo:table-cell> <fo:table-cell padding="2px" text-align="right"> <fo:block>Weeks</fo:block> </fo:table-cell> <fo:table-cell padding="2px" text-align="right"> <fo:block>Rate</fo:block> </fo:table-cell> <fo:table-cell padding="2px" text-align="right"> <fo:block>Quantity</fo:block> </fo:table-cell> <fo:table-cell padding="2px" text-align="right"> <fo:block>VATCode</fo:block> </fo:table-cell> <fo:table-cell padding="2px" text-align="right"> <fo:block>Discount</fo:block> </fo:table-cell> <fo:table-cell padding="2px" text-align="right"> <fo:block>Value</fo:block> </fo:table-cell> </fo:table-row> </fo:table-header> <fo:table-body> <xsl:apply-templates select="//ROOT/Invoice/HireItem"/> </fo:table-body> </fo:table>
This creates the header record for the list of items that have been hired by the customer on this particular contract. The apply-templates section creates and formats each of the records as cells of table shown in the previous code.
The XSLT code is shown here:
<xsl:template match="//ROOT/Invoice/HireItem"> <fo:table-row background-color="white" width="100%"> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="ProductCode"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="Description"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="FromDate"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="ToDate"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="Weeks"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid" text-align="right"> <fo:block> <xsl:value-of select="Rate"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid" text-align="right"> <fo:block> <xsl:value-of select="Quantity"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid" text-align="right"> <fo:block> <xsl:value-of select="VATCode"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid" text-align="right"> <fo:block> <xsl:value-of select="Discount"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid" text-align="right"> <fo:block> <xsl:value-of select="Value"/> </fo:block> </fo:table-cell> </fo:table-row> </xsl:template>
If this code is included with the previous XSLT code, the PDF in Figure 4-14 is produced.
Figure 4-14
You created the header row of the hire items table as a fo:table-header because the number of records may cause the table to be rendered on several pages. If the number of hire items requires a new page, the header row is rendered on the second page.
If you add a sufficient number of hire items to the source XML, the rendering of the second page is shown in Figure 4-15.
Figure 4-15
Any rows included under the fo:table-header element are rendered by default on each page of the table. The same concept applies to any records contained in a fo:table-footer element. This can be overridden by using the table-omit-header-at-break and the table-omit-footer-at-break attributes of a fo:table element. If you set the values of these attributes to true, you stop the rendering of the header record and any footer records on any page other than the start page (in the case of the header) or last page (in the case of the footer) of the table.
The same XML source rendered with the table-omit-header-at-break attribute set to true results in the second page being rendered as shown in Figure 4-16.
Figure 4-16
Next, you want to render the sale items in a table similar to the hire items. The columns are slightly different but the mechanism to render them is mainly the same.
The XSLT code to create and setup the columns for the sale item table is shown here:
<fo:table> <fo:table-column padding="2px" padding-top="5px" column-width="10cm"/> <fo:table-column padding="2px" column-width="5cm"/> <fo:table-column padding="2px" column-width="3cm"/> <fo:table-column padding="2px" column-width="3cm"/> <fo:table-column padding="2px" column-width="3cm"/> <fo:table-column padding="2px" column-width="3cm"/> <fo:table-header border-style="solid"> <fo:table-row background-color="white" border-style="none"> <fo:table-cell number-columns-spanned="6"> <fo:block font-family="serif" font-weight="bold" font-size="20pt"> Sale Items </fo:block> </fo:table-cell> </fo:table-row> <fo:table-row background-color="silver"> <fo:table-cell> <fo:block>Description</fo:block> </fo:table-cell> <fo:table-cell> <fo:block>Date</fo:block> </fo:table-cell> <fo:table-cell> <fo:block>Quantity</fo:block> </fo:table-cell> <fo:table-cell> <fo:block>Price</fo:block> </fo:table-cell> <fo:table-cell> <fo:block>VATCode</fo:block> </fo:table-cell> <fo:table-cell> <fo:block>Value</fo:block> </fo:table-cell> </fo:table-row> </fo:table-header> <fo:table-body> <xsl:apply-templates select="//ROOT/Invoice/SaleItem"/> </fo:table-body> </fo:table>
The apply-templates section for the sale item rows is again very much like the hire items table and is shown here.
<xsl:template match="//ROOT/Invoice/SaleItem"> <fo:table-row keep-with-next="always" background-color="white" width="100%"> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="Description"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="Date"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="Quantity"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="Price"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="VATCode"/> </fo:block> </fo:table-cell> <fo:table-cell border-left-style="solid" padding="2px" border-right-style="solid"> <fo:block> <xsl:value-of select="Value"/> </fo:block> </fo:table-cell> </fo:table-row> </xsl:template>
We have introduced one major difference in the rendering of the table. The row attribute keep-with-next has been used and set to the value always. This means that where possible, the rows of the table are rendered in the same area (in most cases, in the same page). If this attribute is omitted, the sale items table is rendered directly after the hire items table regardless of whether all the records in the sales item data set can be displayed on the same page. Figure 4-17 shows this scenario.
Figure 4-17
Setting keep-with-next to true means that the sales item table split in Figure 4-17 is (where possible) rendered in a contiguous area.
Finally, you want to create a summary table giving the total values for the invoice. The table does not introduce any new features, but the code is presented to complete the example.
<fo:table display-align="after" text-align="right" font-family="serif" font-weight="bold" font-size="18pt"> <fo:table-header border-style="none"> <fo:table-row background-color="white" border-style="none"> <fo:table-cell number-columns-spanned="2" text-align="left" padding-top="10px"> <fo:block font-family="serif" font-weight="bold" font-size="20pt"> Invoice Totals </fo:block> </fo:table-cell> </fo:table-row> </fo:table-header> <fo:table-column padding="2px" column-width="23cm"/> <fo:table-column padding="2px" column-width="4cm"/> <fo:table-body> <fo:table-row border-style="none"> <fo:table-cell padding-top="5px" padding-right="10px"> <fo:block>Net</fo:block> </fo:table-cell> <fo:table-cell padding-top="5px" background-color="silver"> <fo:block>£<xsl:value-of select="//ROOT/Invoice/PreVATTotal"/></fo:block> </fo:table-cell> </fo:table-row> <fo:table-row> <fo:table-cell padding-right="10px"> <fo:block>VAT</fo:block> </fo:table-cell> <fo:table-cell background-color="silver"> <fo:block>£<xsl:value-of select="//ROOT/Invoice/VAT1"/> </fo:block> </fo:table-cell> </fo:table-row> <fo:table-row> <fo:table-cell padding-right="10px"> <fo:block>Total</fo:block> </fo:table-cell> <fo:table-cell background-color="silver"> <fo:block>£<xsl:value-of select="//ROOT/Invoice/InvoiceTotal"/></fo:block> </fo:table-cell> </fo:table-row> </fo:table-body> </fo:table>
The example in full is available for download from this books companion Web site. It renders as shown in Figure 4-18
Figure 4-18