Sign In/My Account | View Cart  
advertisement


Listen Print

An AxKit Image Gallery
by Barrie Slaymaker | Pages: 1, 2, 3, 4

The Configuration

Here's how we configure AxKit to do all of this:

    ##
    ## 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 text/xsl \
                      Apache::AxKit::Language::LibXSLT

        AxAddStyleMap application/x-saxmachines \
                      Apache::AxKit::Language::SAXMachines

    </Directory>

    
    <Directory "/home/me/htdocs/04">
        # Enable XML directory listings (see Generating File Lists)
        AxHandleDirs On

        #######################
        # Begin pipeline config
        AxAddRootProcessor application/x-saxmachines . \
            {http://axkit.org/2002/filelist}filelist
        PerlSetVar AxSAXMachineClass "My::ProofSheetMachine"

        # The absolute stylesheet URLs are because
        # I prefer to keep stylesheets out of the
        # htdocs for security reasons.
        AxAddRootProcessor text/xsl file:///home/me/04/rowsplitter.xsl \
            {http://axkit.org/2002/filelist}filelist

        AxAddRootProcessor text/xsl file:///home/me/04/metamerger.xsl \
            {http://axkit.org/2002/filelist}filelist

        AxAddRootProcessor text/xsl file:///home/me/04/captionstyler.xsl \
            {http://axkit.org/2002/filelist}filelist

        AxAddRootProcessor text/xsl file:///home/me/04/pagestyler.xsl \
            {http://axkit.org/2002/filelist}filelist
        # End pipeline config
        #####################

        # This is read by My::ProofSheetMachine
        PerlSetVar MyColumns 5

        # This is read by My::ProofSheet
        PerlSetVar MyMaxX 100

        # Send thumbnail image requests to our
        # thumbnail generator
        <FilesMatch "^\.">
            SetHandler  perl-script
            PerlHandler My::Thumbnailer
            PerlSetVar  MyMaxX 100
            PerlSetVar  MyMaxY 100
        </FilesMatch>
        
    </Directory>

The first <Directory> section contains the AxKit directives we introduced in article 1 and a new stylesheet mapping for application/x-saxmachines that allows us to use a SAX machine in the pipeline. Otherwise, all of the configuration directives key to this example are in the <Directory "/home/me/htdocs/04"> section.

We saw basic examples of how AxKit works with the Apache configuration engine in article 1 and article 2 in this series. We'll use this photo gallery application to demonstrate many of the more powerful mechanisms in a future article.

By setting AxHandleDirs On, we tell AxKit to generate the <filelist> document (described in the section Generating File Lists) in the 04 directory and below.

Then it's off to configure the pipeline for the 04 directory hierarchy. To do this, we take advantage of the fact that AxKit places all elements in the filelist document in to the namespace http://axkit.org/2002/filelist. The AxAddRootProcessor's third parameter causes AxKit to look at all documents it serves from the 04 directory tree and check to see whether the root element matches the namespace and element name.

This is specified in the notation used by James Clark in his introduction to XML namespaces.

If the document matches, and all AxKit-generated filelists will, then the MIME type and the stylesheet specified in the first two parameters are added to the pipeline. The four AxAddRootProcessor directives add the SAX machine and the four XSLT filters we described in the section "The Pipeline".

When loading a SAX machine into the pipeline, you can give it a simple list of SAX filters (there are many available on CPAN) and it will build a pipeline of them. This is done with a (not shown) PerlSetVar AxSAXMachineFilters "..." directive. The limitation with this directive is that you cannot pass in any initialization values to the filters and we want to.

So, instead, we use the PerlSetVar AxSAXMachineClass "My::ProofSheetMachine" to tell the Apache::AxKit::Language::SAXMachines module to load the class My::ProofSheetMachine and let that class construct the SAX machine.

The final part of the configuration uses a <Files> section to forward all requests for thumbnail images to the mod_perl handler in My::Thumbnailer.

Walking the Pipeline

Now that we have our filters in place, let's walk the pipeline and take a look at each filter and what it emits.

Generating File Lists

<filelist> document's position in the processing pipeline

First, here's a look at the <filelist> document that feeds the chain. This is created by AxKit when it serves a directory request in much the same way that Apache creates HTML directory listings. AxKit only generates these pages when AxHandleDirs On directive. This causes AxKit to scan the directory for the above screenshot and emit XML like (whitespace added, repetitive stuff elided):

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE filelist PUBLIC
      "-//AXKIT/FileList XML V1.0//EN"
      "file:///dev/null"
    >
    <filelist xmlns="http://axkit.org/2002/filelist">
      <directory
        atime="1032276941"
        mtime="1032276939"
        ctime="1032276939"
        readable="1"
        writable="1"
        executable="1"
        size="4096" >.</directory>
      <directory ...>..</directory>
      <directory ...>Mary</directory>
      <directory ...>Jim</directory>
      <file mtime="1031160766" ...>a-look.jpeg</file>
      <file mtime="1031160787" ...>a-lotery.jpeg</file>
      <file mtime="1031160771" ...>a-lucky.jpeg</file>
      <file mtime="1032197214" ...>a-look.meta</file>
      <file mtime="1035239142" ...>foo.html</file>
      ...
    </filelist>

The emboldened bits are the pieces of data we want to display: some filenames and their modification times. Some things to notice:

  • All of the elements -- most importantly the root element as we'll see in a bit -- are in a special namespace, http://axkit.org/2002/filelist, using the xmlns= attribute (see James Clark's introduction for details).
  • The entries are in unsorted order. We might want to allow the user to sort by different attributes someday, but this means that we at least need to sort the results somehow.
  • They contain the complete output from the stat() system call as attributes, so we can use the mtime attribute to derive a modification time.
  • There are files in there (a-look.meta and foo.html) that we clearly should not be displayed as images.
  • The filename for a-look.jpeg is not emboldened: We'll use the <title> element from the a-look.meta file instead.
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE filelist PUBLIC
      "-//AXKIT/FileList XML V1.0//EN"
      "file:///dev/null"
    >
    <filelist xmlns="http://axkit.org/2002/filelist">
      <directory
        atime="1032276941"
        mtime="1032276939"
        ctime="1032276939"
        readable="1"
        writable="1"
        executable="1"
        size="4096" >.</directory>
      <directory ...>..</directory>
      <directory ...>Mary</directory>
      <directory ...>Jim</directory>
      <file mtime="1031160766" ...>a-look.jpeg</file>
      <file mtime="1031160787" ...>a-lotery.jpeg</file>
      <file mtime="1031160771" ...>a-lucky.jpeg</file>
      <file mtime="1032197214" ...>a-look.meta</file>
      <file mtime="1035239142" ...>foo.html</file>
      ...
    </filelist>

My::ProofSheetMachine

My::ProofSheetMachine's position in the processing pipeline.

The processing pipeline is kicked off with a set of three SAX filters built by the My::ProofSheetMachine module:

    package My::ProofSheetMachine;
    
    use strict;
    
    use XML::SAX::Machines qw( Pipeline );
    use My::ProofSheet;
    use XML::Filter::TableWrapper;
    
    sub new {
        my $proto = shift;
        return bless {}, ref $proto || $proto;
    }
    
    sub get_machine {
        my $self = shift;
        my ( $r ) = @_;
    
        my $m = Pipeline(
            My::Filelist2Data
            => My::ProofSheet->new( Request => $r ),
            => XML::Filter::TableWrapper->new(
                ListTags => "{}images",
                Columns  => $r->dir_config( "MyColumns" ) || 3,
            ),
        );
    
        return $m;
    }
    
    1;

This module provides a minimal constructor, new() so it can be instantiated (this is an Apache::AxKit::Language::SAXMachines requirement, we don't need that for our sake). AxKit will call the get_machine() method once each request to obtain the SAX machine is used. SAX machines are not reused from request to request.

$r is a reference to the Apache request object (well, actually, to an AxKit subclass of it). This is passed into My::ProofSheet, which uses to interact query some httpd.conf settings, to control AxKit's cache, and to probe the filesystem through Apache.

$r is also queried in this module to see whether there is a MyColumns setting for this request, with a default in case, it's not. The ListTags setting tells XML::Filter::TableWrapper to segment the image list produced by the first two filters into rows of images (preparing it to be an HTML table, in other words).

The need to pass parameters like this to the SAX filters is the sole reason we're using a SAX machine factory class like this. This class is specified by using PerlSetVar AxSAXMachineClass; if we didn't need to initialize the filters like this, then we could have listed them in a PerlSetVar AxSAXMachineFilters directive. For more details on how SAX machines are integrated with AxKit, see the man page

Currently, only one SAX machine is allowed in an AxKit pipeline at a time (though different pipelines can have different machines in them). This is a limitation of the configuration system more than anything and may well change if need be. However, if we need to add SAX processors to the end of the machine, then the PerlSetVar AxSAXMachineFilters can be used to insert site-specific filters after the main machine (and before the XSLT processors).

My::Filelist2Data

My::Filelist2Data's position in the processing pipeline.

Converting the <filelist> into a proofsheet takes a bit of detailed data munging. This is quite easy in Perl, so the first step in our pipeline is to convert the XML file listing into data. XML::Simple provides this functionality for us, and we overload it so we can grab the resulting data structure and pass it on:

    package My::Filelist2Data;
    
    use XML::Simple;
    @ISA = qw( XML::Simple );
    
    use strict;
    
    sub new {
        my $proto = shift;
        my %opts = @_;
    
        # The Handler value is passed in by the Pipeline()
        # call in My::ProofSheetMachine.
        my $h = delete $opts{Handler};
    
        # Even if there's only one file element present,
        # make XML::Simple put it in an ARRAY so that
        # the downstream filter can depend on finding an
        # array of elements and not a single element.
        # This is an XML::Simple option that is almost
        # always set in practice.
        $opts{forcearray} = [qw( file )];
    
        # Each <file> and <directory> element contains
        # the file name as simple text content.  This
        # option tells XML::Simple to store it in the
        # data member "filename".
        $opts{contentkey} = "filename";
    
        # This subroutine gets called when XML::Simple
        # has converted the entire document with the
        # $data from the document.
        $opts{DataHandler} = sub {
            shift;
            my ( $data ) = @_;
    
            # If no files are found, place an array
            # reference in the right spot.  This is to
            # to simplify downstream filter code.
            $data->/2002/09/24/axkit.html      ||= [];
    
            # Pass the data structure to the next filter.
            $h->generate( $data );
        } if $h;
    
        # Call XML::Simple's constructor.
        return $proto->SUPER::new( %opts );;
    }
    
    1;

Sending a data structure like this between SAX machines using a non-SAX event is known as "cheating." But this is Perl, and allowing you to cheat responsibly and judiciously is one of Perl's great strengths. This works and should work for the foreseeable future. If you're planning on doing something like this for a general purpose filter, then it behooves you to also provide set_handler and get_handler methods so your filter can be repositioned after instantiation (something XML::SAX::Machines do if need be), but we don't need to clutter this single-purpose example.

The <filelist> document gets converted to a Perl data structure where each element is a data member in a HASH or an array, like (data elided and rearranged to relate well to the source XML):

    {
      xmlns => 'http://axkit.org/2002/filelist',
      directory => [
        {
          atime      => '1032276941'
          mtime      => '1032276939',
          ctime      => '1032276939',
          readable   => '1',
          writable   => '1',
          executable => '1',
          size       => '4096',
          content    => '.',
        },
        {
          ...
          content    => '..',
        },
        {
          ...
          content    => 'Mary',
        },
        {
          ...
          content    => 'Jim',
        }
      ]
      file => [
        {
          mtime      => '1031160766',
          ...
          content    => 'a-look.jpeg',
        },
        {
          mtime      => '1031160787',
          ...
          content    => 'a-lotery.jpeg',
        },
        {
          mtime      => '1031160771',
          ...
          content    => 'a-lucky.jpeg',
        },
        {
          mtime      => '035239142',
          ...
          content    => 'foo.html',
        },
        ...
      ],
    }

Pages: 1, 2, 3, 4

Next Pagearrow