Sign In/My Account | View Cart  
advertisement


Listen Print

Taglib TMTOWTDI
by Barrie Slaymaker | Pages: 1, 2

Upstream Filters good for?

So what is XSLT upstream of an XSP processor good for? You can do many things with it other than implementing LogicSheets. One use is to implement branding: altering things like logos, site name, and perhaps colors, or other customization, like administrator's mail addresses on a login page that is shared by several sub-sites.

A key advantage of doing transformations upstream of the XSP processor is that the XSP process caches the results of upstream transformations. XSP converts whatever document it receives in to Perl bytecode in memory and then just runs that bytecode if none of the upstream documents have changed.

Another use is to convert source documents that declare what should be on a page to XSP documents that implement the machinery of a page. For instance, a survey site might have the source documents declare what questions to ask:

    <survey>
      <question>
        <text>Have you ever eaten a Balut</text>
        <response>Yes</response>
        <response>No</response>
        <response>Eeeewww</response>
      </question>
      <question>
        <text>Ok, then, well how about a nice haggis</text>
        <response>Yes</response>
        <response>No</response>
        <response>Now that's more like it!</response>
      </question>
      ...
    </survey>

XSLT can be used to transform the survey definition in to an XSP page that uses the PerForm taglib to automate form filling, etc. This approach allows pages to be defined in terms of what they are instead of how they should work.

You can also use XSLT upstream of the XSP processor to do other things, like translate from a limited or simpler domain-specific tagset to a more complex or general purpose taglib written as a taglib module. This can allow you to define taglibs that are easier to use in terms of more powerful (but scary!) taglibs that are loaded in to the XSP processor.

My::SimpleWeatherTaglib

A new-ish taglib helper module has been bundled in recent AxKit releases: Jörg Walter's SimpleTaglib (the full module name is Apache::AxKit::Language::XSP::SimpleTaglib). This module performs roughly the same function as Steve Willer's TaglibHelper, but supports namespaces and uses a feature new to Perl, subroutine attributes, to specify the parameters and result formatting instead of a string.

Here is My::SimpleWeatherTaglib:

    package My::SimpleWeatherTaglib;

    use Apache::AxKit::Language::XSP::SimpleTaglib;

    $NS = "http://slaysys.com/axkit_articles/weather/";

    package My::SimpleWeatherTaglib::Handlers;

    use strict;
    require Geo::Weather;

    ## Return the whole report for fixup later in the processing pipeline
    sub report :  child(zip) struct({}) {
        return 'Geo::Weather->new->get_weather( $attr_zip );'
    }

    1;

The $NS variable defines the namespace for this taglib. This module uses the same namespace as my_weather_taglib.xsl and My::WeatherTaglib, because all three implement the same taglib (this repetetiveness is to demonstrate the differences between the approaches). See the Mixing and Matching Taglibs section to see how My::WeatherTaglib and My::SimpleWeatherTaglib can both be used in the same server instance.

My::SimpleWeatherTaglib then shifts gears in to a new package, My::SimpleWeatherTaglib::Handlers to define the subroutines for the taglib tags. Using a virgin package like this provides a clean place with which to declare the tag handlers. SimpleTaglib looks for the modules in the Foo::Handlers package if it's use()d in the Foo package (don't use require for this!).

My::SimpleWeatherTaglib requires Geo::Weather and declares a single tag, which handles the <weather:report> tag in weather1.xsp (which we'll show in a moment).

The require Geo::Weather; instead of use Geo::Weather; is to avoid importing subroutines in to our otherwise ...::Handlers namespace which might look like a handler.

There's something new afoot in the declaration for sub report: subroutine attributes. Subroutine attributes are a new feature of Perl (as of perl5.6) that allow us to hang additional little bits of information on the subroutine declaration that describe it a bit more. perldoc perlsub for the details of this syntax. Some attributes are predefined by Perl, but modules may define others for their own purposes. In this case, the SimpleTaglib module defines a handful of attributes, some of which describe what parameters the taglib tag can take and others which describe how to convert the result value from the taglib implementation into XML output.

The child(zip) subroutine attribute tells the SimpleTaglib module that this handler expects a single child element named zip in the taglib's namespace. In weather1.xsp, this ends up looking like:


    <weather:report>
      <!-- Get the ?zip=12345 from the URI and pass it
           to the weather:report tag as a parameter -->
      <weather:zip><param:zip/></weather:zip>
    </weather:report>

The text from the <weather:zip> element (which will be filled in from the URI query string using the param: taglib) will be made available in a variable named $attr_zip at request time. The fact that the text from an element shows up in a variable beginning with $attr_ is confusing, but it does actually work that way.

The struct({}) attribute specifies that the result of this tag will be returned as a Perl data structure that will be converted into XML. Geo::Weather->new->get_weather( $zip ) returns a HASH reference that looks like:

    {
      'city'  => 'Pittsburgh',
      'state' => 'PA',
      'cond'  => 'Sunny',
      'temp'  => '77',
      ...
    };

The struct attribute tells SimpleTaglib to turn this in to XML like:


    <city>Pittsburgh</city>
    <state>PA</state>
    <cond>Sunny</cond>
    <temp>77</temp>
    ....

The {} in the struct({}) attribute specifies that the result nodes should be not be in a namespace (and thus have no namespace prefix), just like the static portions of our weather1.xsp document. This is one of the advantages that SimpleTaglib has over other methods: It's easier to emit nodes in different namespaces. To emit nodes in a specific namespace, put the namespace URI for that namespace inside the curlies: struct({http://my.namespace.com/foo/bar}). The {} notation is referred to as James Clark (or jclark) notation.

Now, the tricky bit. Harkening back to our discussion of how XSP is implemented, remember that the XSP processor compiles the XSP document into Perl code that is executed to build the output document. As XSP compiles the page, it keeps a lookout for tags in namespaces handled by taglib modules that have been configured in with AxAddXSPTaglib. When XSP sees one of these tags, it calls in to the taglib module--My::SimpleWeatherTaglib here--for that namespace and requests a chunk of Perl source code to compile in place of the tag.

Taglibs implemented with the SimpleTaglib module covered here declare handlers for each taglib tag (sub report, for instance). That handler subroutine is called at parse time, not at request time. Its job is to return the chunk of code that will be compiled and then run later, at request time, to generate the output. So report() returns a string containing a snippet of Perl code that calls into Geo::Weather. This Perl code will be compiled once, then run for each request.

This is a key difference between the TaglibHelper module that My::WeatherTaglib used in the previous article and the SimpleTaglib module used here. SimpleTaglib calls My::SimpleWeatherTaglib's report() subroutine at compile time whereas TaglibHelper quietly, automatically arranges to call My::WeatherTaglib's report() subroutine at request time.

This difference makes SimpleTaglib not so simple unless you are used to writing code that generates code that will be compiled and run later. On the other hand, "Programs that write programs are the happiest programs in the world" (Andrew Hume, according to a few places on the net). This is true here because we are able to return whatever code is appropriate for the task at hand. In this case, the code is so simple that we can return it directly. If the work to be done was more complicated, then we could also return a call to a subroutine of our own devising. So, while a good deal less simple than the approach taken by TaglibHelper, this approach does offer a bit more flexibility.

SimpleTaglib's author does promise that a new version of SimpleTaglib will offer the "call this subroutine at request time" API which I (and I suspect most others) would prefer most of the time.

I will warn you that the documentation for SimpleTaglib does not stand on its own, so you need to have the source code for an example module or two to put it all together. Beyond the overly simple example presented here, the documentation refers you to a couple of others. Mind you, I'm casting stones while in my glass house here, because nobody has ever accused me of fully documenting my own modules.

For reference, here is the weather1.xsp from the previous article, which we are reusing verbatim for this example:

    <?xml-stylesheet href="NULL"        type="application/x-xsp"?>
    <?xml-stylesheet href="weather.xsl" type="text/xsl"?>
    <?xml-stylesheet href="as_html.xsl" type="text/xsl"?>

    <xsp:page
        xmlns:xsp="http://www.apache.org/1999/XSP/Core"
        xmlns:util="http://apache.org/xsp/util/v1"
        xmlns:param="http://axkit.org/NS/xsp/param/v1"
        xmlns:weather="http://slaysys.com/axkit_articles/weather/"
    >
    <data>
      <title><a name="title"/>My Weather Report</title>
      <time>
        <util:time format="%H:%M:%S" />
      </time>
      <weather>
        <weather:report>
          <!-- Get the ?zip=12345 from the URI and pass it
               to the weather:report tag as a parameter -->
          <weather:zip><param:zip/></weather:zip>
        </weather:report>
      </weather>
    </data>
    </xsp:page>

The processing pipeline and intermediate files are also identical to those from the previous article, so we won't repeat them here.

Mixing and Matching Taglibs using httpd.conf

As detailed in the first article in this series, AxKit integrates tightly with Apache and Apache's configuration engine. Apache allows different files and directories to have different configurations applied, including what taglibs are used. In the real world, for instance, it is sometimes necessary to have part of a site to use a new version of a taglib that might break an old portion.

In the server I used to build the examples for this article, for instance, the 02/ directory still uses My::WeatherTaglib from the last article, while the 03/ directory uses the my_weather_taglib.xsl for one of this article's examples and My::SimpleWeatherTaglib for the other. This is done by combining Apache's <Directory> sections with the AxAddXSPTaglib directive:

    ##
    ## Init the httpd to use our "private install" libraries
    ##
    PerlRequire startup.pl

    ##
    ## AxKit Configuration
    ##
    PerlModule AxKit

    <Directory "/home/me/htdocs">
        Options -All +Indexes +FollowSymLinks

        # Tell mod_dir to translate / to /index.xml or /index.xsp
        DirectoryIndex index.xml index.xsp
        AddHandler axkit .xml .xsp

        AxDebugLevel 10

        AxTraceIntermediate /home/me/axtrace

        AxGzipOutput Off

        AxAddXSPTaglib AxKit::XSP::Util
        AxAddXSPTaglib AxKit::XSP::Param

        AxAddStyleMap application/x-xsp \\
                      Apache::AxKit::Language::XSP

        AxAddStyleMap text/xsl \\
                      Apache::AxKit::Language::LibXSLT
    </Directory>

    <Directory "/home/me/htdocs/02">
        AxAddXSPTaglib My::WeatherTaglib
    </Directory>

    <Directory "/home/me/htdocs/03">
        AxAddXSPTaglib My::SimpleWeatherTaglib
    </Directory>

See How Directory, Location and Files sections work from the apache httpd documentation (v1.3 or 2.0) for the details of how to use <Directory> and other httpd.conf sections to do this sort of thing.

Help and thanks

Jörg Walter as well as Matt Sergeant were of great help in writing this article, especially since I don't do LogicSheets. Jörg also fixed a bug in absolutely no time and wrote the SimpleTaglib module and the AxTraceIntermediate feature.

In case of trouble, have a look at some of the helpful resources we listed in the first article.

Copyright 2002, Robert Barrie Slaymaker, Jr. All Rights Reserved.