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
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 thexmlns=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 themtimeattribute to derive a modification time. - There are
files in there (
a-look.metaandfoo.html) that we clearly should not be displayed as images. - The filename for
a-look.jpegis not emboldened: We'll use the<title>element from thea-look.metafile 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
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

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',
},
...
],
}



