Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Getting Fancy with FOP-3 : Page 3

Creating XSL-FO Using XSLT
The full code to describe even a relatively simple XSL-FO document like this one can easily be overwhelming if you attempt to write the XSL-FO code by hand, especially because there's a great deal of repetition. Ideally, rather than coding a FO document manually (which you should do once, but only once) it's far better to create an XSLT document that will handle the transformation for you. Fortunately, such transformations are fairly easy to generate.

For example, the file createFOP.xsl contains the transformation for this document. The <xsl:stylesheet> element declaring the stylesheet namespaces requires two namespaces—one for XML-FO and one for XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo = "http://www.w3.org/1999/XSL/Format" version="1.0">

Additionally, set the method attribute of the <xsl:output> so the stylesheet generates xml output (method="xml") and so the result has elements indented, and does not include the XML declaration.

<xsl:output method="xml" media-type="text/xml" indent="yes"/>

The next step to create any kind of document navigator is to figure out how to handle unspecified children. For instance, while the stylesheet uses both the <column> and <copyright> elements, the XSLT handles them directly in the template that matches the <document> element. To ensure that the content of these elements don't just "spill out" to the output stream, the stylesheet supports a template matching otherwise untrapped elements or attributes and simply fails to pass them to the output stream. Text elements, on the other hand, usually do need to be passed to the output stream. The following set of match templates handles both of these problems:

<xsl:template match="*|@*"/> <xsl:template match="text()"> <xsl:copy/> </xsl:template>

The <document> template match puts in most of the boilerplate code, including the page dimension definitions and the footer, header and body region. It attempts to match elements in the order that they are encountered (more or less) in the source document, though in general it's a good idea to explicitly declare the ordering in the stylesheet itself.

<xsl:template match="document"> <fo:root> <fo:layout-master-set> <fo:simple-page-master master-name="mainPage" page-height="11in" page-width="8.5in" margin-top="0.75in" margin-bottom="1in" margin-left="1.25in" margin-right="1.25in"> <fo:region-body margin-top="0.25in" margin-bottom="0.75in" margin-left="0in" margin-right="0in"/> <fo:region-before extent="0.5in"/> <fo:region-after extent="0.75in"/> </fo:simple-page-master> <fo:page-sequence-master master-name="simpleDoc" > <fo:repeatable-page-master-alternatives> <fo:conditional-page-master-reference master-name="mainPage" /> </fo:repeatable-page-master-alternatives> </fo:page-sequence-master> </fo:layout-master-set> <fo:page-sequence master-name="simpleDoc"> <fo:static-content flow-name="xsl-region-before"> <fo:block font-size="8pt" font-family="sans-serif" border-after-color="black" border-after-style="solid" border-after-width="0.1pt"> <xsl:value-of select="column"/>: <xsl:value-of select="title"/> </fo:block> </fo:static-content> <fo:static-content flow-name="xsl-region-after"> <fo:block font-size="8pt" font-family="serif" text-align="right"> Copyright <xsl:value-of select="copyright"/> - Page <fo:page-number/> </fo:block> </fo:static-content> <fo:flow flow-name="xsl-region-body"> <xsl:apply-templates select="*"/> </fo:flow> </fo:page-sequence> </fo:root> </xsl:template>

Much of the remainder of the stylesheet generates the appropriate

<fo:block> elements with the prerequisite attributes, and generally have the form:

<xsl:template match="document/title"> <fo:block font-size="24pt" font-family="serif" font-weight="bold" background-color="blue" color="white" line-height="32pt" padding-before="4pt" padding-left="4pt" space-after.optimum="15pt"> <xsl:apply-templates select="*|@*|text()"/> </fo:block> </xsl:template>

The <xsl:apply-templates> element instructs the XSLT processor to find all child elements, attributes and text blocks for the current node and apply the relevant transformation to them. Because the code for handling unknown XML elements already exists, if the stylesheet doesn't explicitly handle a child element in the source, it simply passes the text of that unknown element into the output stream.

The one single contentious area in this stylesheet involved displaying XML code. XSL-FO does not have an element analogous to the HTML <xmp>, which preserves the content and formatting of HTML and XML code. Consequently, to display large blocks of code, you must break each line into its own distinct <fo:block>. Because it's unlikely that you would want to wrap thousands of lines of code manually, a better solution is to automate the process, in this case with a named template called codeWrite:

<xsl:template match="code"> <fo:block font-size="10pt" font-family="Courier" line-height="12pt" text-align="left" padding-left="0.5in" padding-right="0.5in" space-after.optimum="10pt" space-before.optimum="5pt"> <xsl:call-template name="codeWrite"> <xsl:with-param name="buffer" select="string(.)"/> </xsl:call-template> </fo:block> </xsl:template> <xsl:template name="codeWrite"> <xsl:param name="buffer"/> <xsl:variable name="bufferBefore" select="substring-before($buffer,'&#13;')"/> <!-- note that the <xsl:variable name="bufferTest" select="translate($bufferBefore, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvxyz1234567890-=_+& <xsl:variable name="spaceCount" select="string-length( substring-before($bufferTest,'#'))"/> <fo:block margin-left= "0.{number($spaceCount) * 5}in"> <xsl:value-of select="$bufferBefore"/> </fo:block> <xsl:variable name="newBuffer" select="substring-after ($buffer,'&#13;')"/> <xsl:choose> <xsl:when test="$newBuffer != ''"> <xsl:call-template name="codeWrite"> <xsl:with-param name="buffer" select="$newBuffer"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <fo:block> <xsl:value-of select="$newBuffer"/> </fo:block> </xsl:otherwise> </xsl:choose> </xsl:template>

The codeWrite routine accepts a block of text and scans for carriage returns. After it finds and extracts the first part of the string, it then determines how much white space is at the beginning of the string (by turning everything else but spaces into hash characters and then retrieving the part of the string before the first encountered hash). The length of this whitespace is then converted into a margin value to indicate how far to indent the line of code.

The routine then passes the remainder of the buffered text (after the first carriage return) as a parameter to a recursive codeWrite call, where the whole process repeats with the truncated string. Eventually, th routine can't retrieve any more strings and the recursive call ends. Because the buffer is written to the output stream each time, this scheme walks through each line of the code. Note that for this to work with XML code, the code must be in a CDATA element:

<code>< ![CDATA[ <text>Here is some text</text> ]] ></code>

Just as with generating HTML from XML, XSLT transformations into XSL-FO can range from being relatively simple to downright Byzantine. The one major notable difference between HTML (especially with CSS) and XSL-FO stems from the fact that XSL-FO does not define the notion of a CSS-like class attribute. Thus, in general, the associations between a given tag (such as the <code> element) and its rendering in XSL-FO tend to be considerably more redundant than HTML code. XSL-FO was intended primarily for print publication, so this redundancy actually makes a great deal of sense, because XSL-FO is intended to be a target of an XSLT transformation, not a hybrid logical/presentation/DOM structure like HTML.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date