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

advertisement

XML and Modern CGI Applications

XML and Modern CGI Applications

December 12, 2001

Introduction

Perl owes a fair amount of its widespread adoption to the Web. Early in the Web's history it was discovered that serving static HTML documents was simply not a very interactive environment, and, in fairly short order. the Common Gateway Interface (CGI) was introduced. Perl's raw power and forgiving nature made it a natural for this new environment, and it very quickly became the lingua franca of CGI scripting.

CGI is not without its weaknesses, and despite well-funded campaigns from a number of software vendors, CGI is still widely used and shows no signs of going away anytime soon. This month we will be looking at a module that offers a new take on CGI coding, Christian Glahn's CGI::XMLApplication.

Borrowing heavily from the Model-View-Controller pattern, CGI::XMLApplication provides a modular, XML-based alternative to traditional CGI scripting. The typical CGI::XMLApplication project consists of three parts: a small executable script that provides access to the application, a logic module that implements various handler methods that are called in response to the current state of the application, and one or more XSLT stylesheets that are used, based on application-state, to transform the data returned by the module into something a browser can display to its user.

Example One -- A CGI XSLT Gateway

CGI::XMLApplication presumes that the designers and developers involved in a given project have chosen to use XSLT stylesheets to separate application logic from presentation, and it seeks to make that separation as painless and straightforward as possible. Developers need only ensure that the setStylesheet callback returns the location of the XSLT stylesheet that is appropriate for the current application state. The transformation of the DOM tree which the application builds, the optional passing of XSLT parameters to the transformation engine, and the delivery of the transformed content to the browser (along with the appropriate HTTP headers) are completely invisible to the user.

To underscore this separation, our first example will not be a Web application in the traditional (HTML forms-based data entry) sense, but rather a generic XSLT gateway that can be added to the server's cgi-bin to allow an entire directory tree of XML content to be transformed for requesting browsers, while making the transformation invisible to users as well as stylesheet and document authors.

The first step is to create the CGI script that connects the client request with our application. We want the XML documents being served to be easily navigable by URL, and we want to make creating hyperlinks between those documents straightforward. Thus we will create our CGI script without a file extension, so that it's just another node in a URL path -- everything to the right of that node is interpreted in the context of a virtual document root that contains the XML content. In this case, we will name the CGI script stylechooser.

use strict;
use lib '/path/to/secure/webapp/libs';
use XSLGateway;
use CGI qw(:standard);my $q = CGI->new();
my %context = ();
my $gateway_name = 'stylechooser';

After loading the appropriate modules and setting a few script-wide variables we begin by adding fields to the %context hash that will be passed along to the class that handles the bulk of the application's logic. For this application we pass through only the portion of the requested URL that is to the right of the path to the script itself (the REQUEST entry), and an optional STYLE key that contains the data stored in the querystring parameter "style"

$context{REQUEST} = $q->url(-path => 1);
$context{REQUEST} =~ s/^$gateway_name\/?//;
$context{REQUEST} ||= 'index.xml';
$context{STYLE}   = $q->param('style') if $q->param('style');

Finally we create an instance of the XSLGateway logic class and process the request by calling it's run method, passing in the %context hash as the sole argument.

my $app = XSLGateway->new();
$app->run(%context);

That's all there is to the CGI script. Now we create the XSLGateway module that does most of the work:

package XSLGateway;

use strict;
use vars qw(@ISA);
use CGI::XMLApplication;
use XML::LibXML;

@ISA = qw(CGI::XMLApplication);

As I mentioned in the introduction, CGI::XMLApplication works via event callbacks: a given method in the application class is executed based on the value of a particular input field (usually the name of the button used to submit the form). There are two other callbacks that must be implemented: the selectStylesheet and requestDOM methods.

The selectStylesheet method is expected to return the full filesystem path to the relevant XSLT stylesheet. To keep things simple, we are presuming that the stylesheets will only reside in a single directory. We are allowing some added flexibility by providing for alternate styles via the $context->{STYLE} field (which, you will recall, contains the data passed through by the "style" query param).

sub selectStylesheet {
    my $self = shift;
    my $context = shift;
    my $style = $context->{STYLE} || 'default';
    my $style_path = '/opt/www/htdocs/stylesheets/';
    return $style_path . $style . '.xsl';
}

Next we need to create the requestDOM method, which is expected to return an XML::LibXML DOM representation of the XML document being transformed for delivery. Since our gateway is only serving static files, we need only to parse the appropriate document using XML::LibXML and return the resulting tree.

sub requestDOM {
    my $self = shift;
    my $context = shift;
    my $xml_file = $context->{REQUEST} || 'index.xml';
    my $doc_path = '/opt/www/htdocs/xmldocs/';
    my $requested_doc = $doc_path . $xml_file;

    my $parser = XML::LibXML->new;
    my $doc = $parser->parse_file($requested_doc);
    return $doc;
}
Comment on this article Are you still using CGI? With XML? Share your experience with the community in our forums.
Post your comments

Now we make sure our CGI script is safely executable in the server's cgi-bin and upload a few XML documents and an XSLT stylesheet or two to the appropriate directories. And we're ready to go. A request to http://localhost/cgi-bin/stylechooser/mydocs/somefile.xml will select the file mydocs/somefile.xml from the /opt/www/htdocs/xmldocs/ directory, transform it with the stylesheet default.xsl in the /opt/www/htdocs/stylesheets/ folder and deliver the transformed data to the client.

You can obviously extend this basic framework as you need. You might, for example, allow for some sort of lookup table in the stylechooser CGI script that maps certain files or directories to particular XSLT stylesheets, or you might set and read HTTP cookies that configure the users' preferred style to allow for personalized, skinnable Web sites. But for beginning basic XML/XSLT Web publishing with a minimum of hassle and setup, it's hard to beat the kind of simplicity that this tiny CGI application provides.

Example Two -- A Simple Shopping Cart

For our final example we will use CGI::XMLApplication to create a simplified version of the workhorse of demo Web applications, the shopping cart.

As with the previous example, the part of the application that's exposed via CGI-BIN is extremely small. All we do is initialize our CustomerOrder application class and call its run() method. This time, though, we pass through the contents of CGI.pm's Vars hashref as the PARAMS field of the %context hash.

use strict;
use CGI qw(:standard);
use lib '/path/to/secure/webapp/libs';
use CustomerOrder;
my $q = CGI->new();
my %context = ();
$context{PARAMS} = $q->Vars;

my $app = CustomerOrder->new();
$app->run(%context);

For this example we will assume that the product information for our order application is stored in a relational database. The product list is modest so we can get away with having three screens in our application: the main data entry screen where users enter the quantities for one or more products they wish to order, a confirmation screen that shows the contents of the cart and totals the cost of the items selected, and a thank you screen that indicates that the order has been processed. In the interest of simplicity we do not discuss necessities like a shipping and billing data entry screen.

package CustomerOrder;

use strict;
use vars qw(@ISA);
use CGI::XMLApplication;
use XML::LibXML::SAX::Builder;
use XML::Generator::DBI;
use DBI;

@ISA = qw(CGI::XMLApplication);

Pages: 1, 2

Next Pagearrow