Random musings from my awakening dementia...
10.10.2004  
Ant Web Publishing Tutorial, Part 2
 

I'm quite interested in the concept of software components and how those ideas can be applied to Java code. Thoughts or ideas I have on this subject get dropped here for the benefit of humanity and my own hubris.

© 2004-2005, Howard Abrams



Except where otherwise noted, all original content is licensed under a Creative Commons License.
See details.

In Part 1 of this tutorial on web publishing with my Ant system, I gave a basic overview that would allow you to build fairly rich static web pages from templates. In this episode, I’m going to make things a little more interesting.

The template system has a ${filename} tag available to it, which is just the filename of the original data file without any extension to it. I mentioned before that you could use it to display a specific image associated with each data file, like:

<img src="/images/${filename}.jpg"/>

But when I was building my work website, I didn’t have a special image for each file, but I had some of them. If I didn’t have an image for a particular file, then I wanted to have an image chosen by which directory they were stored in. That is, all of the “articles” would have the same image, all of the “news updates” would have another image, etc.

For example, the data file, products/yellowj/features.txt would match the following files in a particular directory (that I specify):

  • features.gif
  • yellowj.jpg
  • products.png

In other words, I need the ability to add more data available to my templates in a dynamic (programmatic) way.

Similar to my idea of being able to add formatting engines as plugins to my architecture, I did the same thing with adding auxiliary data. The idea is I would create a “plugin” that would calculated data for a template based on some conditions.

For my specific need, I created a “filepairing” plugin that I shoved into my Ant build.xml file like:

<webtemplate …>
    <auxdata name="image" engine="FilePairing">
        <variable name="dir" value="htmldata/images"/>
    </auxdata>
</webtemplate>

The <auxdata> tag hooks up a plugin to my <webtemplate> task. It has two required parameters, a “name” and an “engine.” The name specifies the label that the templates will use to access any data this plugin spits out. In this case, the data will be called image.

The “engine” attribute specifies the plugin. In this case, it is called “FilePairing” and it gives the template a ${image} tag (the value of the “name” attribute) that first matches another file in another directory, and if it doesn’t find one, it starts walking up the parent directory tree looking for a match.

But I also need to specify this directory. Attribute values that are given to the plugin directly are specified using the <variable> tag, and yes, you can have multiple ones. What variables should be specified vary from plugin to plugin.

Next step is to add a reference to it in my FreeMarker template:

<#if $image?exists>
    <p align="right">
        <img src="/images/${image}" alt="" class="photo"/>
    </p>
</#if>

This plugin serves a very specific need that I had on a couple of web sites, but that is the beauty of starting with an open architecture that allows plugins, and then building your own extensions to allow such plugins. It is now time to demonstrate something truly diabolical…

I hate having to add a new file to a directory and then needing to go around and update the navigational bars. I believe those should be calculated. Allow me to introduce you to the LocalNavBar plugin.

When the system is apply a content file to a template, this plugin gives the template some information about all of that file’s siblings in the directory that holds the content file. The code to add to your build file is:

<webtemplate …>
    <auxdata name="siblings" engine="LocalNavBar" />
</webtemplate>

Yeah, it looks pretty harmless.

In my template, I now need to access this data. The data comes to the template as a List of Maps, where each Map contains information about each of the files in that directory. So, in FreeMarker, I could do something like:

<ul class="menu">
    <#list siblings as sib>
        <li>
            <a href="${sib.file}">${sib.title}</a>
        </li>
    </#list>
</ul>

If you were using Velocity, the code would look like:

<ul class="menu">
    #foreach( $sib in $siblings )
      <li>
        <a href="${sib.file}">${sib.title}</a>
      </li>
    #end
</ul>

But what if you didn’t like the order that it gave it to you? Well, FreeMarker allows you to sort a list, so if we wanted to sort by the file modification time, we could change things around to be:

<#list siblings?sort_by("filedate") as sib>
    <li>
        <a href="${sib.file}">${sib.title}</a>
    </li>
</#list>

If you had a special order that you wanted to maintain, then add a metadata line to your files. For instance, you could have your text files contain a “date” line:

date=2004-10-04

Then you could sort_by("date"), or whatever the metadata name was used.

You’re probably wondering what other file attributes are available. They are pretty much the same as what a template knows about the content file:

  • title … the document title
  • file … the name of the file (with extension)
  • filename … the name of the file (without extension)
  • filesize … the number of characters
  • filedate … the file system’s understanding of the content’s date. This is usually the modification date.
  • firstparagraph … the text between the first <p>...</p> tags.
  • firstsentence … the first sentence after the first <p>.
  • excerpt … the first couple of sentences where the total number of words are less than 40.

Yeah, that excerpt is interesting, but what if you wanted to have more or less than 40? We give it a variable, like:

<auxdata name="siblings" engine="LocalNavBar">
    <variable name="excerptSize" value="20"/>
</auxdata>

On a similar note, this plugin takes another optional variable that affects how it works, if you change your build file to contain:

<webtemplate …>
    <auxdata name="siblings" engine="LocalNavBar">
        <variable name="index" value="true"/>
    </auxdata>
</webtemplate>

By default, it ignores files whose name is “index”. The idea here is that the index file gives a table of contents of the other files and often appears like a parent, and not a sibling to the other files. But the “index” variable allows you to include those sorts of files.

Let’s put it all together for a big conclusion. We begin with the Ant’s build file section:

<webtemplate src="${dir.htm}" dest="${dir.html}"
        template="default.ftl" 
        engine="FreeMarker" includes="**/*.htm" ext=".htm2" 
        propertyFile="${stg.main}/default.properties">
    <auxdata name="siblings" engine="LocalNavBar">
        <variable name="excerptSize" value="20"/>
        <variable name="index" value="true"/>
    </auxdata>
</webtemplate>

And we’ll make a navigational bar where the siblings are listed in order of size, because the longer the document, the more important it is. We will also display more information about each sibling, including the date it was last modified in a “long” format, i.e. April 8, 2003:

<ul class="menu">
    <#list siblings?sort_by("filesize") as sib>
        <li>
            <a href="${sib.file}">${sib.title}</a> …
            ${sib.excerpt} (${sib.filedate?string.long})
        </li>
    </#list>
</ul>