In my last article on XSL Formatting Objects (XSL-FO), we took a look at the basics. We created a simple page master with one body region and then wrote some text onto the page. However, XSL-FO offers a lot more functionality and flexibility. In this article, we'll take a closer look at regions.
XSL-FO defines several levels of areas with which we can work. The highest level is the page, and the next level after this is the region. A region is, in the simplest terms, a section of a page. Each page contains five regions, which correspond to different areas of the page. Using regions, it's possible to place content appropriately.
For example, consider a page of a book. The page could be divided up into multiple regions, each containing different bits of information. The text of the page, for instance, would go into one region, and the page number would go into another. Later, we'll be able to use the various regions to design the page of a book.
Regions are defined inside of the page master to which they belong. In the last article, we examined the body region, defined using the region-body tag. There are more region types, but we'll start by looking at the body region, since it's the most important. A body region definition might look something like this:
<fo:region-body region-name="xsl-region-body" />
Note that the name "xsl-region-body" is traditionally used for body regions. In fact, it's the default value, so there's no need to explicitly state the name like we did above. You're free to use whatever name you want, but if you're lacking in creativity, then this value will do. Besides, when the region is referenced elsewhere in the XSL-FO document, the region's type will be unambiguous. The other regions also have default names like this.
This region contains, as the name suggests, the body of the page. This is where the page's primary content is contained. In the case of a page out of a book, the actual text would go here. In order to actually place the text within the region, the flow element is used. For example, to flow content into the body region we created earlier, we'd do something like this:
<fo:flow flow-name="xsl-region-body"> <fo:block>Hello, world.</fo:block></fo:flow>
As you can see, working with body regions alone is very simple.
Now let's take a look at the other regions available, one at a time.
The next region we'll take a look at is the top region. The top region is placed, of course, on the top of the page. This is a page's header. In the case of a page out of a book, the name of the book or chapter might go here.
In order to define this region, the region-before tag is used (this may seem odd, since the area it refers to is more intuitively called the top of the page or the header, but the region contains the content "before" everything else). This tag goes inside of the page master definition, just like the body region definition, but it must be placed after the body region's definition. This holds true for all non-body regions.
Here's a basic example of a master page with two region definitions, one for the body region and one for the top region:
The default name for the top region is "xsl-region-before."
The next logical step here, since we just looked at the top region, is to look at the bottom region. This region is traditionally called the page's footer. The corresponding tag, however, is called region-after, since the region is located after the other regions. Here's an example definition:
The default name for the bottom region is "xsl-region-after."
We've now looked at the regions corresponding to the top and bottom of a page. We can also place text on the left and right sides of a page. The region on the left of a page is the left region, and it's defined using the region-start tag:
Its default name is, of course, "xsl-region-start."
The region on the right of a page is the right region, and it's defined using the region-end tag:
As expected, "xsl-region-end" is the default name.
Like the top and bottom regions, the names of the left and right regions may seem a bit non-intuitive, but they might make sense if we take a look at exactly where the regions are located on the page. The top region is located before all other regions, and the bottom region is located after all other regions. So, the left and right regions are both located entirely between the top and bottom regions. Think of them as the start and end of this middle section of the page.
In order for a region to be of any use, it needs content. For the body region, the flow tag was used to flow content across multiple pages, creating new pages as necessary:
<fo:flow flow-name="xsl-region-body"> <fo:block>Hello, world.</fo:block></fo:flow>
However, for the other regions, flowing doesn't really make sense. In the instance of a book page, the bottom region would contain a simple page number. There is nothing to flow to the next page. This would be confusing, but even if you could envision a scenario where this might be useful, FOP does not support it. All flow elements must point to body regions, or else the processor will refuse to process the document.
Rather than using the flow element to have text flow across pages, then, the content of each non-body region must be static. This makes sense, though. In our book page example, the content of these regions would be more or less uniform throughout the entire book. The top region would contain the book title or the chapter title, and the bottom region would contain the page number. No surprises here.
The static-content element is used for this. It is defined similarly to the flow element, within a page-sequence element. However, any static-content elements must be placed before the flow element.
Let's say that we wanted to fill the top region with the title of a book. The definition would look something like this:
<fo:block>Title of a Book</fo:block>
Again, the difference between the flow element and the static-content element is primarily behavioral, not syntactical. Thus, using static-content elements shouldn't be very difficult.
Throughout the article, I've been discussing how regions might be used in designing a page out of a book. Let's apply a bit of what we've gone over to create a short and very simple example, so that you can see regions in action. We'll go ahead and create the page that we've been talking about so that you can see how the finished product would look. Plus, there's also one additional point that I've left out so far, but I'd rather show it to you than merely tell you about it.
The page will be pretty standard, but the idea here is to create a short example illustrating how the pieces fit together. On the top of the page will be the name of the book, and on the bottom of the page will be the page number. In the center of the page will be, of course, the text of the page.
We'll start from the very beginning. First, we need to provide an XML declaration and a root element:
<?xml version="1.0" encoding="UTF-8"?><fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"></fo:root>
Next, we need to define the master page. We'll assign the name "BookPage" to the master page. In order to achieve the desired look, we'll have to use three regions: the body region, the top region and the bottom region. Remember that the body region must be defined first. The default region names will do fine.
All that needs to be done now is to actually create a page or pages using the page master. To populate the top and bottom regions, the static-content element is used, and to populate the body region, the flow element is used. Remember that any static-content elements must go before the flow element.
<fo:layout-master-set> <fo:simple-page-master master-name="BookPage"page-width="110mm" page-height="178mm" margin="10mm"> <fo:region-body /> <fo:region-before /> <fo:region-after /> </fo:simple-page-master></fo:layout-master-set>
In the top region, we'll just put the name of the book. Often, a book will alternate what's at the top of the page. One page will have the name of the book, and then the opposite page will have the name of the chapter, or the author's name, or some additional piece of information like that. While it's possible to do this with XSL-FO, some additional functionality would be necessary, and that would take us into some entirely different topics. So, for this example, the name of the book will appear on every page.
In the bottom region, the page number will appear. This is done using the page-number element, which is very simple and doesn't need any sort of additional explanation. Finally, in the body region, the content will appear. I'll just put a short sentence, because I don't want to take up too much space, but you'll probably want to think up some more content (or do some copying and pasting) to make the example more complete.
Here's what all of that looks like:
<fo:page-sequence master-reference="BookPage"> <fo:static-content flow-name="xsl-region-before"> <fo:block>Using XSL</fo:block> </fo:static-content> <fo:static-content flow-name="xsl-region-after"> <fo:block> <fo:page-number /> </fo:block> </fo:static-content> <fo:flow flow-name="xsl-region-body"> <fo:block>This is a short example. Replace this with whatever block of text you want in order to make it longer.</fo:block> </fo:flow></fo:page-sequence>
You might want to replace the body text with something longer to get an idea of what the page will look like filled with content, but in any case, the document is now ready to be put through the processor. Process it using whatever options you like. The easiest way would probably be just to use fop's built-in viewer to see the result:
$ fop examplePage.fo -awt
Immediately, you should notice something very wrong. The content in the top and bottom regions actually overlaps with the body content. This is because all regions aren't created equally. The non-body regions are, in a way, part of the body region. In order to separate them, margins must be defined for the body region. This is easy to do. Simply replace the region definitions in the original XSL-FO file with the following definitions:
<fo:region-body margin-top="30mm" margin-bottom="30mm" /><fo:region-before extent="30mm" /><fo:region-after extent="30mm" />
As you can see, we simply added two attributes to the region-body element. The first is margin-top, which specifies a length for the top margin, and the second is margin-bottom, which specifies a length for the bottom margin. That much is pretty self-explanatory. Then, we added an extent attribute to each of the non-body region definitions. This simply specifies the extent of the region. Without these attributes, you'll get an error when you try to process the page.
The output should now look much better, with each bit of text in its proper place rather than all overlapping each other. Of course, there are a number of improvements that could be made. For example, the title and page number could both be centered, and the content of the top region could be alternated with the pages, as was discussed earlier. However, since those those improvements must be made outside of the functionality provided by regions, and are thus outside the scope of this article, I'll leave them to you.
Now you should have a basic understanding of regions in XSL-FO. Go out and try to create some more documents with regions. Experiment a bit to see just what you can create with regions.