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

advertisement

XML and Modern CGI Applications
by Kip Hampton | Pages: 1, 2

After loading the necessary modules and declaring our inheritance from CGI::XMLAplication we begin by creating the various event callbacks that are associated with the various states (or screens, if you prefer) of our application. First we must register those events by setting the registerEvents() callback to return a list of the handlers that our application will implement over and above the handlers that are called by default. In this case we will register the order_confirm and order_send callbacks which set the SCREENSTYLE field in the %context hash. Later we will use this property to define which of the three XSLT stylesheet will be used to render the order data to the client.

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

Note that events are mapped to the actual subroutines that implement them using the convention event_<eventname>; so, for example, the "order_confim" event is implemented by the event_order_confim subroutine. Also be aware that the various events are selected by CGI::XMLApplication based on its ability to find a form parameter that has the same name as one of the registered events. To fire the order_confirm handler, a form widget in the previous screen must contain a form field named "order_confirm" that submits a non-null value. In our example we have taken the easy route named each form's submit buttons appropriately to achieve the desired results.

# event registration and event callbacks
sub registerEvents {
    return qw( order_confirm order_send );
}

sub event_order_confirm {
    my ($self, $context) = @_;
    $context->{SCREENSTYLE} = 'order_confirm.xsl';
}

sub event_order_send {
    my ($self, $context) = @_;
    $context->{SCREENSTYLE} = 'order_send.xsl';
}

The event_default callback is executed if no other handlers were requested. Here we use it only to set the SCREENSTYLE field to an appropriate value.

sub event_default {
    my ($self, $context) = @_;
    $context->{SCREENSTYLE} = 'order_default.xsl';
}

The event_init callback is called for every request and before any other handlers are called. This makes it quite useful for initializing the parts of the application that will be used by the other handlers. In this case we use it to return the initial DOM tree that contains the product information from the database using the fetch_recordset() method, storing that tree in the %context hash for later use.

sub event_init {
    my ($self, $context) = @_;
    $context->{DOMTREE} = $self->fetch_recordset();
}

With the state-handler methods complete we need to implement the required selectStylesheet and requestDOM methods.

As in the first example, we will assume that all the application's stylesheets are in the same directory on the server; all we will do here is return that path with the value from $context->{SCREENSTYLE} (that was set by the state handler), appended to the end.

# app config and helpers
sub selectStylesheet {
    my ($self, $context) = @_;
    my $style = $context->{SCREENSTYLE};
    my $style_path = '/opt/www/htdocs/stylesheets/cart/';
    return $style_path . $style;
}

Before we look at the requestDOM handler, let's step through our fetch_recordset helper method first to provide a better context.

Recall that we are selecting the information about the products in our cart from a relational database, but the data that we are passing to the XSLT processor must be a DOM tree. Rather than building the DOM programmatically from scratch we make life easier by using Matt Sergeant's fine XML::Generator::DBI, which generates SAX events from data returned from an SQL SELECT executed via Tim Bunce's legendary DBI module. Creating the required DOM tree is a matter of setting an instance of XML::LibXML::SAX::Builder (which creates an XML::LibXML DOM tree from SAX events) as the handler and for that driver.

sub fetch_recordset {
    my $self = shift;
    my $sql = 'select id, name, price from products';

    my $dbh = DBI->connect('dbi:Oracle:webclients',
                           'chico',
                           'swordfish')
      || die "database connection couldn't
              be initialized: $DBI::errstr \n";

    my $builder = XML::LibXML::SAX::Builder->new();
    my $gen = XML::Generator::DBI->new(Handler      => $builder,
                                       dbh          => $dbh,
                                       RootElement  => 'document',
                                       QueryElement => 'productlist',
                                       RowElement   => 'product');

    my $dom = $gen->execute($sql) || die "Error Building DOM Tree\n";
    return $dom;
}

The fetch_recordset method makes an otherwise complex task trivial; but the DOM tree it returns contains only part of the information that we want to send to the client. We also need to capture the quantities entered from any previous submissions within this session, and we need to provide a running total of the products ordered.

sub requestDOM {
    my ($self, $context) = @_;
    my $root = $context->{DOMTREE}->getDocumentElement();
    my $grand_total = '0';

To include the current order quantities as part of the larger document we will loop over the product elements and append <quantity> and <item-total> child elements to each "row". The quantity values are available from the $context->{PARAMS} field that contains all the data that may have been posted to the form. See the associated stylesheets in this month's sample code for details about how the forms fields are created and how the data is passed around.

    foreach my $row ($root->findnodes('/document/productlist/product')) {
        my $id         = $row->findvalue('id');
        my $cost       = $row->findvalue('price');
        my $quantity   = $context->{PARAMS}->{$id} || '0';
        my $item_total = $quantity * $cost;
        $grand_total  += $item_total;

        # add the order quantity and item totals to the tree.
        $row->appendTextChild('quantity', $quantity);
        $row->appendTextChild('item-total', $item_total);
    }
 

Finally we add a bit of additional meta information about the order by appending an <instance-info> element to the root element with an <order-total> child element which contains the total cost of the currently selected items.

    $grand_total ||= '0.00';
    my $info = XML::LibXML::Element->new('instance-info');
    $info->appendTextChild('order-total', $grand_total);
    $root->appendChild($info);

    return $context->{DOMTREE};
}

1;

Careful readers will have noticed that our simplified cart does not actually do anything with the submitted data during the order_send event. The truth is that deciding where the data goes is often the most site-specific part of any cart application. The complete application, including stylesheets, is available in this month's sample code.

Conclusions

I was initially skeptical of CGI::XMLApplication. As a card-carrying AxKit user I've grown accustomed to its speedy mod_perl foundation, and I've gotten quite comfortable generating my dynamic database-driven XML content using AxKit's eXtensible Server Pages implementation. The reality is, though, that the luxury of a dedicated XML publishing/application server like AxKit is beyond the reach and need of many developers. There is a large gap between the "just print it" of traditional CGI scripts and the high-octane XML-centric goodness of tools like AxKit. CGI::XMLApplication fills that gap nicely.

CGI::XMLApplication offers a clean, modular approach to CGI scripting that encourages a clear division between content and presentation, and that alone makes it worth a look. Perhaps more importantly, though, I found that it just got out of my way while handing enough of the low-level details to let me focus on the task at hand. And that's a sure sign of a good tool.

Resources



1 to 5 of 5
  1. xml file here (kind of)
    2001-12-21 18:03:51 M Hunter
  2. CGI::XMLApplication
    2001-12-19 13:01:09 Jim Hobbs
  3. versions and data for XSLGateway
    2001-12-17 12:00:44 Mike Barry
  4. Re: resource
    2001-12-14 16:50:47 Kip Hampton
  5. resource
    2001-12-14 03:27:08 Henrik Binggl
1 to 5 of 5