XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Introducing XML::SAX::Machines, Part One
by Kip Hampton | Pages: 1, 2

Example -- Adding Custom Tag Libraries to XHTML

One of the more interesting ideas to emerge in the Web development world in recent years is the notion of custom tag libraries (or taglibs, for short). In a taglib implementation one or more custom tags are defined and the server application evaluates and expands or replaces those tags with the result of running some chunk of code on the server. This allows document authors to add reusable bits of server-side functionality to their pages without the hair loss associated with embedding code in the documents.

For this month's example we will write a mod_perl handler that allows us to create our own custom taglibs. We will do this by creating SAX filters that transform the various tags in our library into the desired results. ANd we'll use SAX::Machines within our Apache handler to manage the filter chain.

First, we need to define our taglib. To keep the example simple we start off with only two tags: an <include> tag that provides a way to insert the contents of an external document defined by the uri attribute at the location of the tag, and a <fortune> tag that inserts a random quote.

To avoid possible collision with the elements allowed in the documents that will contain the tags from our taglib, we will quarantine them in their own XML namespace and bind that namespace to the prefix "widget".

Here is an example of a simple XHTML document containing our custom tags:

<?xml version="1.0"?>
<html xmlns:widget="http://localhost/saxpages/widget">
  <head>
    <title>My Cool Taglib-Enabled Page</title>
  </head>
  <body>
    <widget:include uri="/path/to/widgets/common_header.xml"/>
    <p>
     Today quote is:
    </p>
      <pre><widget:fortune/></pre>
    <p>
    Thanks for stopping by.
    </p>
    <widget:include uri="/path/to/widgets/common_footer.xml"/>
  </body>
</html>

Now let's create our SAX filters to expand our custom tags. We'll write the filter that include an external XML document, first.

package Widget::Include;
use strict;

use vars qw(@ISA $WidgetURI);
@ISA = qw(XML::SAX::Base);
$WidgetURI = 'http://localhost/saxpages/widget';

After a bit of initialization we get straight to the SAX event handlers. In the start_element handler we examine the current element's NamespaceURI and LocalName properties to see if we have an "include" element in our widgets namespace. If it finds one, it further checks for an uri attribute, and, if it finds one, it passes that file name on to a new parser instance using the current filter as the handler.

sub start_element {
    my ( $self, $el ) = @_;

    if ( $el->{NamespaceURI} eq $WidgetURI &&
         $el->{LocalName} eq 'include' ) {

         if ( defined $el->{Attributes}->{'{}uri'} ) {
             my $uri = $el->{Attributes}->{'{}uri'}->{Value};
             my $parser = XML::SAX::ParserFactory->parser( Handler => $self );
             $p->parse_uri( $uri );
         }
    }

If we did not get an element with the right name in the right namespace we forward the event to the next filter in the chain.

    else {
        $self->SUPER::start_element( $el );
    }
}

We do a similar test in the end_element event handler; forwarding the events that we are not interested in.

sub end_element {
    my ( $self, $el ) = @_;

    $self->SUPER::end_element( $el ) unless
        $el->{NamespaceURI} eq $WidgetURI and
        $el->{LocalName} eq 'include';

}

Also in Perl and XML

OSCON 2002 Perl and XML Review

XSH, An XML Editing Shell

PDF Presentations Using AxPoint

Multi-Interface Web Services Made Easy

Perl and XML on the Command Line

That's it. Since this filter inherits from XML::SAX::Base we need only implement the event handlers that are required for the task at hand. All other events will be safely forwarded to the next filter/handler.

The filter that implements the <widget:fortune> tag is very similar. We check to see if the current element is named "fortune" and is bound to the correct namespace. If so, we replace the element with the text returned from a system call to the fortune program. If not, the events are forwarded to the next filter.

package Widget::Fortune;
use strict;

use vars qw(@ISA $WidgetURI);
@ISA = qw(XML::SAX::Base);
$WidgetURI = 'http://localhost/saxpages/widget';

sub start_element {
    my ( $self, $el ) = @_;

    if ( $el->{NamespaceURI} eq $WidgetURI &&
         $el->{LocalName} eq 'fortune' ) {
         my $fortune = `/usr/games/fortune`;
         $self->SUPER::characters( { Data => $fortune } );
    }
    else {
        $self->SUPER::start_element( $el );
    }
}

sub end_element {
    my ( $self, $el ) = @_;

    $self->SUPER::end_element( $el ) unless
        $el->{NamespaceURI} eq $WidgetURI and
        $el->{LocalName} eq 'fortune';

}

With the filters out of the way we turn to the Apache handler that will make our filters work as expected for the files on our server. The basic Apache handler module that makes our taglibs work is astonishingly small considering what it provides. We simply create a new instance of XML::SAX::Pipeline then, inside the required handler subroutine, we create a Pipeline machine, passing in the names of the widget filter classes we just created. Then we send the required HTTP headers and call parse_uri on the file being requested by the client.

package SAXWeb::MachinePages;
use strict;
use XML::SAX::Machines qw( :all );

sub handler {
  my $r = shift;

  my $machine = Pipeline(
    "Widget::Include" =>
    "Widget::Fortune" =>
    \*STDOUT
  );

    $r->content_type('text/html');
    $r->send_http_header;
    $machine->parse_uri(  $r->filename );
}

Finally, we need to upload the XML documents to the server and add a small bit to one of our Apache configuration file so our handler is called appropriately. I used

<Directory /www/sites/myhostdocroot >
  <FilesMatch "\.(xml|xhtml)">
    SetHandler perl-script
    PerlHandler SAXWeb::MachinePages
  </FilesMatch>
</Directory>

After restarting Apache, a request to the XML document we created earlier will look something like the following:

<html xmlns:widget='http://localhost/saxpages/widget'>
  <head>
    <title>My Cool Page</title>
  </head>
  <body>
  <div class='header'>
<h2>MySite.tld</h2>
<hr />
</div>
  <p>
  Today quote is:
  </p>
  <pre>The faster we go, the rounder we get.
		-- The Grateful Dead
</pre>
  <p>
  Thanks for stopping by.
  </p>
  <div class='footer'>
<hr />
<p>Copyright 2000 MySite.tld, Ltd. All rights reserved.</p>
</div>
  </body>
</html>

No Webby awards here, to be sure, but the basic foundation is sound and implementing new tags for our tag library is a matter of creating new SAX filter classes and adding them the Pipeline in the Apache handler.

Conclusions

We've only touched the surface of what XML::SAX::Machines can do. Tune in next month when we will delve deeper into the API and show off some of its advanced features.

Resources



1 to 1 of 1
  1. Minor bug in Widget::Include
    2002-04-26 03:47:17 Grant McLean
1 to 1 of 1