Section 5.3.  Top-level structures

  
Prev don't be afraid of buying books Next

5.3 Top-level structures

The basic layout of the web page has already been built by the skeleton template ( 5.2.2 ). In this section, we look at the other generic top-level constructs, in particular navigation menus and blocks. All of them are usually created by pull-oriented trunk templates ( 4.5.1 ).

5.3.1 Menu

Our next task is creating a menu for our web page. The code in Example 5.5 builds the site's main menu reflecting the top level of the site hierarchy (there are four top-level items in the menu in our sample master document, so the menu will have four buttons ).

Example 5.5. Menu templates and related functions.
 <xsl:template name=  "top-menu"  >   <!--  Menu layout code...  -->   <xsl:apply-templates select=  "$master//menu/item"  />   <!--  Menu layout code...  --> </xsl:template> <xsl:template match=  "menu/item"  >   <xsl:variable name=  "src"  select=  "page[1]/@src"  />   <xsl:variable name=  "label"  select=  "   label/translation[@lang=$lang]"  />   <!--  Composing filename for the graphic menu button:  -->   <xsl:variable name=  "filename"  select=  "   concat(   string(1 + count(preceding-sibling::*)),   eg:letters-only($label))"  />   <!--  Generating the menu button image:  -->   <xsl:if test=  "$images='yes'"  >     <xsl:call-template name=  "create-image"  >       <xsl:with-param name=  "label"  select=  "$label"  />       <xsl:with-param name=  "filename"  select=  "   concat($out-img, $filename)"  />     </xsl:call-template>   </xsl:if>   <!--  Menu item layout code...  -->   <xsl:choose>     <xsl:when test=  "$src = $current"  >       <!--  Current page is the root of this branch   (no link, branch active):  -->       <span class="active">         <img src="  {$target-img}{$filename}  .png" alt="  {$label}  "/>       </span>     </xsl:when>     <xsl:when test=  "($src != $current) and (page/@src = $current)"  >       <!--  Current page is one of the pages (not root) of this branch   (link, branch active):  -->       <a href="  {$target-path}{$lang}/{$src}{$out-ext}  "          class="active">         <img src="  {$target-img}{$filename}  .png" alt="  {$label}  "/>       </a>     </xsl:when>     <xsl:when test=  "$src != $current"  >       <!--  Current page does not belong to this branch   (link, branch inactive):  -->       <a href="  {$target-path}{$lang}/{$src}{$out-ext}  "          class="inactive">        <img src="  {$target-img}{$filename}  .png" alt="  {$label}  "/>       </a>     </xsl:when>   </xsl:choose>   <!--  Menu item layout code...  --> </xsl:template> <xsl:function name=  "eg:letters-only"  >   <xsl:param name=  "s"  />   <xsl:value-of select=  "   lower-case(replace($s, '\ ,\.!\?', ''))"  /> </xsl:function> 

As with page-footer , there are two templates here. The first one is callable; it encloses the entire menu into appropriate layout constructions and iterates through the top-level items. The second template is applicable ; it creates one menu item on each invocation.

Items as images. Implementation-wise, the menu can be textual, graphic, Flash, and so on. To illustrate one of the most common practical scenarios, Example 5.5 builds a menu out of static .png images (one per menu item), which are generated by the stylesheet itself ( 5.5.2 ). Layout code is not shown; add according to taste.

For each item, three variables are declared:

  • $src is the pathname (taken from the src attribute) of the item 's first page child;

  • $label is the item's label in the current language; and

  • $filename is the name for the generated image file, made out of the item's number in the sequence of siblings and its label (with spaces and punctuation removed). Using the label ensures that different language versions of the menu will have different button filenames (even though, in our implementation, they are all stored in a common directory).

Then, the create-image template is called; it creates the button image taking the $label and $filename as parameters. For an implementation of this callable template, see Example 5.16 (page 247).

Item states. The core of the menu item template is a choice among several possible relationships between the currently processed menu item and the current page document. Our example differentiates between:

  • the root page of the menu branch headed by the current item: Button image is not linked (you cannot link a page to itself) and has class="active" ;

  • a page that belongs to the current branch but is not its root: Image is linked (so you can ascend to the root of the branch) and has class="active" ;

  • a page that is outside of the current menu branch: Image is linked and has class="inactive" .

The CSS classes active and inactive provide the formatting for the current and non-current menu items. For example, you can use a different background color or margins for the current item.

The complexity range. Of course, you can use totally different formatting options for your menu items (such as DHTML or JavaScript effects), or you can recognize different logical states of an item. For instance, the simplest possible non-hierarchical menu might line up all its items and link them without any correlation with the current page. A slightly more complex setup could skip the current page's item or leave it unlinked .

On the other end of the scale, the three-state distinction in Example 5.5 might be further complicated by an orthogonal set of pages that do not belong in the menu hierarchy and therefore do not affect the formatting or behavior of the menu (but do display the menus themselves ), or by the site's front page that may have a completely different presentation of the menu compared to all other pages of the site.

Submenu organization. Lower levels of the menu hierarchy, if present, may be formatted in a variety of ways. Common options include

  • static submenus: subitems are listed statically under their parent top-level items;

  • orthogonal menus: a subspecies of static submenus, with only one (current) submenu displayed on each page, usually without a visible connection to the main menu (e.g., the main menu may run horizontally across the top of page, and the submenu, vertically in the left marginhence "orthogonal");

  • interactive submenus: same as static, but the lists of subitems are shown and hidden interactively using JavaScript or Dynamic HTML code snippets, for example, on a click or mouseover;

  • collapsing trees: similar to interactive submenus in that subitem branches are shown and hidden interactively, but are more suitable for deeply nested content hierarchies, as you can collapse or expand the tree to arbitrary depth.

We don't need to discuss specific stylesheet templates for any of these options. Once you understand how the main menu template works, there's nothing new in a submenu. For the sake of completeness, however, our summary example (Example 5.21, page 267) contains a simple orthogonal submenu template.

Recursive menu. One interesting possibility is a single recursive menu template that alone builds all levels of the menu hierarchy by calling itself for each submenu level. Such a template would not, however, be too useful unless all of your menu levels use similar formatting (which might be the case, for example, for a collapsing tree menu).

5.3.2 Dynamic menus

One type of menu that we did not cover when discussing source markup in Chapter 3 was the dynamic menu. The reason we missed it was that a dynamic menu is built by the stylesheet not from an XML document but from another data source, such as a database or a list of files in a directory. In other words, there's nothing in any static XML document that would correspond to the final list of items on the page (although your master document may store a reference to the place from which this list will be retrieved by the stylesheet).

Suppose you need to build a dynamic menu that reflects the current list of files in a directory. You want this menu to be built totally automatically, so that you can simply drop or remove files in the "watched" directory, rerun the transformation, and have the menu updated to reflect the changes. How do we go about it?

5.3.2.1 Reading a directory with Java

As mentioned before ( 4.4.3 ), many XSLT processors let you write your own functions in some programming language and call these functions from your XPath expressions. For example, Saxon, itself written in Java, allows you to link up any Java classes and call their methods . [8]

[8] Methods is Java-speak for what most other languages call functions.

String or nodeset? Thus, to watch a directory, all we need to do is write a Java method that takes a directory path as an argument and returns its list of files. This list of files can be returned simply as a stringand XSLT 2.0 with its spiffy new regexp functions will have no problems parsing it. More elegant, of course, would be a method returning a nodeset looking something like this (serialized):


 <file>file1.xml</file> <file>file3.xml</file> <file>oddfile.xml</file> 


Then, we could run a simple xsl:for-each loop over the result returned by such a method. Unfortunately, constructing a nodeset in an extension function is more trouble than it's worth. With some processors, you may have to use DOM classes to create nodes, while with Saxon, you can only return nodes conforming to Saxon's own (optimized and nonportable) tree implementation.

So, we'll go the simple route and write a class with a method returning the list of files in a string, separated by newlines:


 file1.xml file3.xml oddfile.xml 


The dir() method in the files class (Example 5.6) does just that. This class also provides the exists() method that checks for existence of a file (we'll need this later).

While we are at it, you might want to extend the files class with other useful methods, such as moving or deleting files, querying the operating system name and version, etc. These methods may be used in the batch processing code in the stylesheet ( 5.6 ).

Filtering files. If you want the dir() method to list only some of the files (e.g., only *.xml files), you may write another class implementing the FilenameFilter interface [9] and give an instance of that class as an argument to fdir.listFiles . A less cumbersome solution is to filter the returned list in XSLT using ends-with () with the filename extension you are interested in.

[9] java.sun.com/j2se/1.4.2/docs/api/java/io/FilenameFilter.html



Example 5.6. A Java class whose dir() method returns, as a StringBuffer , a newline-separated list of filenames in a given directory.
  package   com.projectname.xslt;   import   java.io.*;   import   java.util.*;   public class   files {   private static  File[]  flist;   public static  boolean  exists  (String  fileName  ) {     File  file  =  new  File (fileName);  return  file.exists ();   }  public static  StringBuffer  dir  (String  dirName  ) {     StringBuffer  sb  =  new  StringBuffer ();     File  fdir  =  new  File (dirName);     flist = fdir.listFiles ();  for  (int  i  = 0; i < flist.length; i ++) {       sb.append (flist[i].getAbsolutePath () +  "  \n  "  );     }  return  sb;   } } 

5.3.2.2 Setting up Java extensions

As this is our first extension class (more are used later in this chapter), let's see how to install it into a Java environment and link to the stylesheet.

Installing a class. Store your files class in a file with the same name and .java extension ( files.java ) and compile it ( javac files.java ). Java expects the resulting .class file to be located in a subdirectory tree that corresponds to the full package name as declared in the class source. Thus, for the com.projectname.xslt package in our example, do this:

  • create a classes directory in any convenient place;

  • create a chain of subdirectories classes/com/projectname/xslt ;

  • place files.class into xslt ;

  • add the full path to classes to your CLASSPATH environment variable by typing


     set CLASSPATH=%CLASSPATH%;full/path/to/classes/ 


    on Windows (note the semicolon) or


     export CLASSPATH=$CLASSPATH:full/path/to/classes/ 


    on Unix (note the colon ).

Plugging it in. In your XSLT stylesheet, you need to declare the new extension class before you can use it. This is done via a namespace declaration, with the class path being the namespace URI. The arbitrary prefix you associate with that URI will be prepended to the function name in XPath expressions. We already did this in the opening xsl:stylesheet tag in Example 5.2 (page 208): The URI com.projectname.xslt.files identifies the files class we've just created.

Other Java-based processors use similar methods of linking up external classes. If your processor is not written in Java, you may have to rewrite the extensions in another language, but the principle remains the same: You use a namespace declaration to identify the source of an external module and then call that module's functions with the corresponding namespace prefix.

5.3.2.3 Example: linking files

Now, to build a menu linking all files in a directory, all you need to do is this ( assuming each file in $dir is well- formed XML and has a /title element for the text of the link):


 <xsl:for-each select=  "tokenize(string(files:dir($dir)), '\n')"  >   <xsl:if test=  "."  >     <a href="  {replace(., $src-ext, $out-ext)}  ">       <xsl:value-of select=  "document(.)/title"  />     </a>   </xsl:if> </xsl:for-each> 


Of note:

  • Unlike 1.0, XSLT 2.0 is a strongly typed language, so we have to explicitly convert the value returned by files:dir() to a string.

  • We need to test for the filename being non-empty ( <xsl:if test="."> ) because the Java method will return a string with a newline at the end, which the tokenize() function will convert into an empty item.

  • The href attribute contains the returned filename with $src-ext replaced by $out-ext . We therefore assume that all source XML files in $dir will get transformed to web pages that we can link. In fact, as we'll see in 5.6 , we can use a similar mechanism to perform such batch transformation from within the stylesheet.

5.3.3 Blocks

Just as there may be many different types of blocks (recall our discussion in 3.1.2 , page 93), you may need to create many different block templates. The key concept is modularity ; make sure that each block template's output properly fits into the corresponding slot in your page skeleton ( 5.2.2 ) or any other places it may need to fit into (for example, some blocks may nest into each other).

What block templates mostly consist of is layout code. You will want to set the block apart from its surroundings by a background, frame, or other means. You'll also need to create standard block accessories, such as heading, icon, author name, and of course the body text. Creative possibilities are endless; you can vary formatting of your blocks depending on how many other blocks are there before or after this block on the page, on the name of the author, on the age of the information, or on anything else.

Just as in the top-level skeleton template, you can use either callable or (more commonly) applicable templates to insert bits of data into their layout slots. Do not use xsl:value-of for this purpose unless you are absolutely sure the element you refer to will always contain flat text with no markup.

The inner core of a block template is usually an xsl:apply-templates without a select attribute. It passes control further into the applicable templates that take care of low-level text markup. So, finally, here's the point where the trunk templates are finished and branch processing starts ( 5.4 ). The pull is overlet's start pushing!

5.3.3.1 Orthogonal blocks

With blocks, the only nontrivial bit is processing the orthogonal content ( 2.1.2.2 , page 51). In our examples in Chapter 3, orthogonal blocks were supposed to be extracted from arbitrary pages of the site. The correspondence between an orthogonal block id , its source document, and the id of the block to be extracted from that source is given in the master document. In page documents, each block either has its own content or provides an idref attribute referring to an orthogonal block declared in the master.

A template with multiple inputs. With regard to formatting, however, there may be no big difference between native and orthogonal blocks. Therefore, it is convenient to have one template handle all blocks with similar formatting, no matter where they come from. Can this be done in XSLT? You bet.

In the block -matching template, we first declare the $from variable storing the nodeset that we will be working on. In Example 5.7, this nodeset may be either an orthogonal block fetched from its original location or the source document's current block node. It may also be a nodeset generated by some dynamic process ( 3.9.1.4 ). Then, we construct our block as usual, but prepend $from to all select values in xsl:apply-templates instructions that fill in the content of the block.

Example 5.7. A block template handling both native and orthogonal blocks.
 <xsl:template match=  "block"  >   <xsl:variable name=  "from"  select=  "   if (@idref != '')   then   document(eg:page-src(@idref, $lang))   //block[@id=current()/@idref]   else . "  />   <!--  Block layout code...  -->   <!--  Creating block heading:  -->   <xsl:apply-templates select=  "$from/heading"  />   <!--  Processing paragraphs:  -->   <xsl:apply-templates select=  "$from/p"  />   <!--  Block layout code...  --> </xsl:template> 

Unabbreviation of orthogonal sources. Note that to get the full pathname of the orthogonal block's source document, we use the eg:page-src() unabbreviation function defined in the shared XSLT library ( 5.1.1 ). This is possible because that function searches both page s and block s in the master document to find out the @src corresponding to a given @id .

 
  
Amazon


XSLT 2.0 Web Development
ASP.Net 2.0 Cookbook (Cookbooks (OReilly))
ISBN: 0596100647
EAN: 2147483647
Year: 2006
Pages: 90

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net