7.2 Other Dynamic XML Techniques


While XSP is the dominant dynamic XML tool, there are other options, which may be appropriate in some cases.

7.2.1 Aggregate Data URIs

An aggregate data URI is simply a way to combine content from more than one source and present it as a single URI-addressable resource. While the uses for this technique are many, the most obvious and intuitive use is building collections of metadata about all or some documents on a given site. Suppose you are publishing an online newsletter. In each publishing period, you publish a few articles while archiving the old ones. Obviously, one feature that such a site needs is an article indexyou do not want the older content to be forgotten and rendered useless just because it is not featured in this period's issue. Furthermore, you know from experience that such indexes are more useful to your visitors when they contain more than just a list of hyperlinked titles. They need a greater level of detail (date published, article summary, etc.) about how the archived articles are presented in the index. When faced with this task, developers traditionally go one of two ways: they either choose to create and maintain the index by hand, or they store the archived articles in a database and use a script to generate the index dynamically. Using XML, AxKit, and aggregate URIs offers a novel , third choice: you can set up alternate styles for the articles themselves to automatically extract the metadata you need, then combine those article-specific bits of data into a single resource that is the content source for the online index.

The advantage here is that filtered aggregated results are cached on disk after the first requestsaving the overhead of reading, parsing, and transforming each individual document.

Example 7-4 shows the stylesheet that extracts the metadata from the articles. To keep things simple, assume that all the articles are marked up using the Simplified DocBook grammar.

Example 7-4. sdocbook_meta.xsl
 <?xml version="1.0"?> <xsl:stylesheet      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"      xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"      xmlns:dc="http://purl.org/dc/elements/1.1/"      version="1.0" > <xsl:param name="request.uri.hostname"/> <xsl:param name="request.uri"/> <xsl:variable name="fullURI" select="concat('http://', $request.uri.hostname, $$ <xsl:template match="article"> <rdf:RDF>   <rdf:Description rdf:about="{$fullURI}">     <dc:format>text/xml</dc:format>     <dc:title><xsl:value-of select="titlearticleinfo/title"/></dc:title>     <xsl:apply-templates select="articleinfo"/>   </rdf:Description> </rdf:RDF> </xsl:template> <xsl:template match="publishername">   <dc:publisher><xsl:value-of select="."/></dc:publisher> </xsl:template>      <xsl:template match="authorgroup"> <dc:creator>   <rdf:Bag>     <xsl:for-each select="author">       <rdf:li>          <xsl:value-of select="concat(firstname, ' ', surname)"/>       </rdf:li>     </xsl:for-each>   </rdf:Bag>    </dc:creator> </xsl:template> <xsl:template match="author">   <dc:creator>     <xsl:value-of select="concat(firstname, ' ', surname)"/>   </dc:creator>  </xsl:template> <xsl:template match="articleinfo">   <xsl:if test="copyrightlegalinfo">     <dc:rights>       <xsl:if test="copyright">         Copyright <xsl:apply-templates select="copyright"/>         <xsl:value-of select="legalinfo"/>       </xsl:if>     </dc:rights>   </xsl:if>   <xsl:variable name="subjects"         select="keywordset/keywordsubjectset/subject/subjectterm"/>    <xsl:if test="count($subjects)">     <dc:subject>       <xsl:choose>         <xsl:when test="count($subjects) > 1 ">           <rdf:Bag>             <xsl:for-each select="$subjects">               <rdf:li><xsl:value-of select="."/></rdf:li>             </xsl:for-each>           </rdf:Bag>         </xsl:when>         <xsl:otherwise>           <xsl:value-of select="$subjects"/>         </xsl:otherwise>       </xsl:choose>     </dc:subject>     </xsl:if>   <xsl:apply-templates select="./*[local-name( ) != 'copyright']"/> </xsl:template> <xsl:template match="copyright">   <xsl:value-of select="year"/>   <xsl:text> </xsl:text>   <xsl:value-of select="holder"/> </xsl:template> <xsl:template match="abstract">   <dc:description><xsl:value-of select="."/></dc:description> </xsl:template>          <xsl:template match="pubdate">     <dc:date><xsl:value-of select="."/></dc:date> </xsl:template> </xsl:stylesheet> 

This simple stylesheet extracts the title, author's name, publisher, date, abstract summary, and several other useful bits of information from a Simplified DocBook article. It presents the information as an RDF/XML document using elements from the Dublin Core Metadata Initiative's suggested grammar. This a nice feature that will probably prove useful beyond your immediate goal, but you are not done yet. You need a way to pull the metadata for each article into a single resource. You can achieve this in several ways. Example 7-5 shows a simple RDF/XML document that lists the articles you want to include into the metadata aggregate.

Example 7-5. article_list.rdf
 <?xml version="1.0"?> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">  <rdf:Seq rdf:about="http://localhost/articles/"       xml:base="http://localhost/articles/">    <rdf:li rdf:resource="smithee.dkb" />    <rdf:li rdf:resource="goldenage.dkb" />    <rdf:li rdf:resource="nonlinear.dkb" />  </rdf:Seq> </rdf:RDF> 

Note the use of the xml:base attribute. This not only saves you from having to type out the full URL for each article, but it provides the base URI when resolving the included documents. Example 7-6 shows the XSLT stylesheet that handles the task of combining the individual metadata results in one document.

Example 7-6. sdocbook_meta_aggregate.xsl
 <?xml version="1.0"?> <xsl:stylesheet      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"      xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"      xmlns:dc="http://purl.org/dc/elements/1.1/"      version="1.0" > <xsl:template match="/"> <rdf:RDF>   <xsl:apply-templates/> </rdf:RDF> </xsl:template> <xsl:template match="rdf:li">   <xsl:variable name="uri" select="concat(@rdf:resource, '?style=meta')"/>   <xsl:copy-of select="document($uri, .)/rdf:RDF/*"/> </xsl:template> </xsl:stylesheet> 

By using the two-argument form of the document( ) function, the XSLT processor uses the base URI that you set using xml:base attribute in the source document to resolve the document's location. In this case, that means that the XSLT processor will make an HTTP request back to your web host for each article, specifying the meta style in the query string, which, in turn , will cause AxKit to apply the sdocbook_meta.xsl stylesheet to that document when returning the result. The sum of all of this is that you can now access all metadata from all the articles listed in the article_files.rdf file via a single URI. The result looks something like this:

 <?xml version="1.0"?> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"                 xmlns:dc="http://purl.org/dc/elements/1.1/">     <rdf:Description rdf:about="http://myhost.tld/articles/smithee.dkb">         <dc:format>text/xml</dc:format>         <dc:title>The History of Alan Smithee</dc:title>         <dc:rights> Copyright 2001 John Q. Pundette, Esq.</dc:rights>         <dc:subject>             <rdf:Bag>                 <rdf:li>History of Cinema</rdf:li>                 <rdf:li>Film Directors</rdf:li>             </rdf:Bag>         </dc:subject>         <dc:creator>John Pundette</dc:creator>         <dc:date>2001</dc:date>         <dc:description> Alan Smithee is credited with directing some of the worst films of all             time-- and that's just the way he likes it. Find out more about this enigmatic and             controversial figure. </dc:description>     </rdf:Description>     <rdf:Description rdf:about="http://myhost.tld/articles/goldenage.dkb">         <dc:format>text/xml</dc:format>         <dc:title/>         <dc:rights> Copyright 2001 John Q. Pundette, Esq.</dc:rights>         <dc:creator>John Pundette</dc:creator>         <dc:date>2001</dc:date>         <dc:description> Hollywood is back with a vengeance, and this time, it's  personal. </dc:description>     </rdf:Description>     <rdf:Description rdf:about="http://myhost.tld/articles/nonlinear.dkb">         <dc:format>text/xml</dc:format>         <dc:title>Some Assembly Required: Why We Need More Non-Linear Plots</dc:title>         <dc:rights> Copyright 2004 Indy Filmsnob</dc:rights>         <dc:creator>Indiana Filmsnob</dc:creator>         <dc:date>2004</dc:date>         <dc:description> Modern viewers have had enough of the Hansel and Gretel  School of plot             development. Let's mix it up, people! </dc:description>     </rdf:Description> </rdf:RDF> 

With this result, you have all the data you need to create a rich, meaningful index for all articles on your site; you only need to process this document with another stylesheet to generate browsable HTML. It does not stop there, though. For example, you may also reuse that same aggregate source to extract all information about articles written by a single author, or ones published during a specific date range. All you need to do is apply the appropriate stylesheets to the aggregate source to get what you need. The following configuration snippet ties together what you have done so far and shows some possibilities for filtering the aggregated metadata:

 # These rules apply to the /articles/ directory <Directory /articles/>     # Add the query string StyleChooser     AxAddPlugin Apache::AxKit::StyleChooser::QueryString          # Transform sdocbook into HTML     <AxStyleName html>         AxAddProcessor text/xsl /styles/sdocbook_html.xsl     </AxStyleName>     # Extract the article's metadata as RDF     <AxStyleName meta>          AxAddProcessor text/xsl /styles/sdocbook_meta.xsl     </AxStyleName>          # HTML is the default style     AxStyle html          # Special rules apply to the metadata aggregate list     <Files article_list.rdf>         # The other styles never apply here         AxResetProcessors         AxAddProcessor text/xsl /styles/sdocbook_meta_aggregate.xsl                  # Show the aggregated data as an HTML index         <AxStyleName html>             AxAddProcessor text/xsl /styles/rdf_aggregate2html.xsl         </AxStyleName>                  # Reuse the aggregated data for inclusions that filter the data          # based on params passed that specify author, date range, etc.         <AxStyleName filtered>             AxAddProcessor text/xsl /styles/rdf_aggregate_filter.xsl         </AxStyleName>                  # HTML is the default         AxStyle html     </Files>      </Directory> 

This strategy still requires someone to manually update the article_list.rdf file as new articles are added. If you want to make the whole thing more or less maintenance free, you could use an XSP page to create the list of articles. (See Example 7-7.)

Example 7-7. article_list.xsp
 <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"           xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <xsp:structure>   <xsp:import>File::Find::Rule::XPath</xsp:import> </xsp:structure> <xsp:logic>     my $web_base = 'http://localhost/';     my $finder = File::Find::Rule::XPath->new( );     $finder->name('*.xml', '*.dkb');     $finder->relative(1);     $finder->xpath('/article');              my @files = $finder->in( $r->document_root . '/articles/'); </xsp:logic> <rdf:RDF>  <rdf:Seq rdf:about="http://localhost/articles/"       xml:base="http://localhost/articles/">   <xsp:logic>    foreach my $path ( @files ) {       <rdf:li rdf:resource="{$path}"/>    }   </xsp:logic>  </rdf:Seq> </rdf:RDF>   </xsp:page> 

This deceptively simple XSP page uses Grant McLean's File::Find::Rule::XPath module from CPAN to search the host's /articles/ directory (and its subdirectories) for all files with a .dbk or .xml extension and those containing a top-level article element. It generates a document with the same structure as the previous static file, except the list is created dynamically.

There is a trade-off here, though. Generating the list dynamically frees you from updating the list of articles, but it also means that all the aggregation processing happens for every request . With the static version, the result is cached on the first request, and the process is not repeated until the list of files changes.

To be sure, most aggregate data URI are not as complex as the one you created here. In fact, most of the time, a few simple XInclude elements do the trick. However, combining transformed resources into one resource that is also transformed to meet a specific need illustrates a powerful technique that would be difficult, if not impossible , to achieve outside a dynamic flexible XML environment such as AxKit.

7.2.2 Application ContentProviders

XSP offers a rich environment in which to generate dynamic XML content, but it is not to everyone's taste. For some, using markup application grammar and tag libraries, which map XML elements to Perl code that generates markup during processing, adds an undesirable layer of indirection to their day-to-day coding; others simply prefer to generate content within a familiar, pure Perl environment. As usual, AxKit's modularity offers an alternative. If XSP is not your style, you may consider creating or using an application ContentProvider instead.

In the most generic sense, an application ContentProvider is nothing more that an alternative Provider class that generates its content dynamically, based on one or more conditions (in contrast to the default Provider that reads the content from a file on the disk). More often, though, an application Provider is a bridge between an existing application environment and AxKit.

To illustrate this utility and the simplicity that using an application ContentProvider can offer, we will examine a simple two-state web application using the SAWA (Simple API for Web Applications) application environment. We will not delve into SAWA in detail here; just know that it implements the Model, View, Controller design pattern on top of a flexible dispatching mechanism that fires a series of registered event handler methods (across multiple component classes, if need be) in response to the current state of the application. The typical SAWA application consists of two parts : a base application class in which the processing logic for the current application state takes place, and an output class in which the concrete interface that represents state is generated. SAWA has the added benefit of an output class that is also an AxKit Provider. This gives SAWA both a way to pass content directly into AxKit as well as a means to control which transformative styles AxKit applies to the content returned.

To show how SAWA and AxKit can work together, we will create a simple application that accepts a bit of user input and then generate a random pairing for the data enteredessentially, a variation of the typical "Find Your (Gangster/Past Life/Hip-Hop DJ) Name" forms that pop up regularly on the Web. Example 7-8 shows the base application class.

Example 7-8. MovieName::Base.pm
 package MovieName::Base; use SAWA::Constants; use SAWA::Event::Simple; use strict; our @ISA = qw( SAWA::Event::Simple ); BEGIN { srand(time( ) ^ ($$ + ($$ << 15))) } sub registerEvents {     return qw/ complete /; } sub event_default {      my $self    = shift;      my $ctxt   = shift;      $ctxt->{style_name} = 'moviename_prompt';      return OK;  }          sub event_complete {     my $self    = shift;     my $ctxt    = shift;     my @param_names = $self->query->param;         my @first_names = map { $self->query->param("$_") } grep { $_ =~ /^first\./ }  @param_names;     my @last_names  = map { $self->query->param("$_") } grep { $_ =~ /^last\./ }   @param_names;     if ( ((scalar @first_names) + (scalar @last_names) != 6)          ( grep { length =  = 0 } @first_names, @last_names ) ) {                 $ctxt->{message} = 'All fields must be filled in. Please try again.';         $ctxt->{style_name} = 'moviename_prompt';         return OK;     }     $ctxt->{first_name} = $first_names[ int( rand( @first_names )) ];     $ctxt->{last_name} = $last_names[ int( rand( @last_names )) ];     $ctxt->{message} = 'Congratulations! Your Movie Star name has been magically determined!';     $ctxt->{style_name} = 'moviename_complete';     return OK; } 1; 

Two event-handler methods are presented here. The first is the event_default( ) handler, which is called if no registered event is called. It simply sets the value for the style_name key in the global context hash reference (that is passed to every handler method) to the value stylea_prompt . (You'll see the effect this has when we examine the output class.) Next is the . event_complete( ) that is called when the client submitted data. In this case, after a little error checking, the handler also sets the style_name key and adds the randomly chosen first and last names to the context hash. Example 7-9 shows the output class in which things get a bit more interesting.

Example 7-9. MovieName::Output.pm
 package MovieName::Output; use XML::LibXML; use SAWA::Constants; use SAWA::Output::XML::AxKit; our @ISA = qw/ SAWA::Output::XML::AxKit /; sub get_style_name {     my $self = shift;     my $ctxt = shift;     $self->style_name( $ctxt->{style_name}  );     return OK; }      sub get_document {     my $self = shift;     my $ctxt = shift;     my $dom  = XML::LibXML::Document->new( );     my $root = $dom->createElement( 'application' );     $dom->setDocumentElement( $root );     my $msg_element = $dom->createElement( 'message' );     $msg_element->appendChild( $dom->createTextNode( $ctxt->{message} ) );     my $fname_element = $dom->createElement( 'first_name' );     $fname_element->appendChild( $dom->createTextNode( $ctxt->{first_name} ) );     my $lname_element = $dom->createElement( 'last_name' );     $lname_element->appendChild( $dom->createTextNode( $ctxt->{last_name} ) );     $root->appendChild( $fname_element );     $root->appendChild( $lname_element );     $root->appendChild( $msg_element );     $self->document( $dom );     return OK; } 1; 

First, this class is a subclass of SAWA::Output::XML::AxKit, which together with Apache::AxKit::Provider::SAWA, provides the bridge between SAWA and AxKit. In practical terms, this means that the data you pass to class member accessors from these output methods are forwarded directly to AxKit. In the case of the get_style_name( ) event handler, you set the class member style_name to the value selected by the event hander in the Base.pm application class. The name selected ( moviename_prompt or moviename_complete ) corresponds to the AxKit-named style blocks whose processing directives are used to transform the content returned.

The get_document( ) event handler simply generates an XML::LibXML::Document instance by which you create the XML (via the DOM interface) that is passed off AxKit to transform. (This SAWA class also allows you to return the content as textual XML markup, but then AxKit has to parse it before transformation. Returning a document object saves a little bit of parsing overhead).

Now you have seen how the style is selected and the content created. Let's look at the configuration that enables this to work seamlessly with AxKit:

 # Load the AxKit<->SAWA "bridge"            PerlModule Apache::AxKit::Provider::SAWA <Location /moviename>     # Set AxKit as the handler for this virtual URI     SetHandler axkit     AxResetProcessors     # Add the request params plug-in so XSLT can access request data     AxAddPlugin Apache::AxKit::Plugin::AddXSLParams::Request     PerlSetVar AxAddXSLParamGroups "Request-Common"     # Set the "bridge" as the AxKit ContentProvider for this resource     AxContentProvider Apache::AxKit::Provider::SAWA          # Add out two SAWA modules to its dispatcher pipeline     SawaAddPipe MovieName::Base     SawaAddPipe MovieName::Output     # Set up the AxKit  named styles that your SAWA application will select to      # create the View for the current application state.     <AxStyleName moviename_prompt>         AxAddProcessor text/xsl /styles/moviename/prompt.xsl         AxAddProcessor text/xsl /styles/moviename/common.xsl     </AxStyleName>     <AxStyleName moviename_complete>         AxAddProcessor text/xsl /styles/moviename/complete.xsl         AxAddProcessor text/xsl /styles/moviename/common.xsl     </AxStyleName> </Location> 

This configuration block loads the Apache::AxKit::Provider::SAWA class as a PerlModule (required, since it implements its own set of custom Apache configuration directives). It then creates a virtual URI, /moviename , which uses that module as the AxKit ContentProvider. Next, the SAWA application and output classes are added to SAWA's pipeline, and the AxKit-named style blocks that the application will select to transform the content are added.

Your little application has two states: the default state ( moviename_prompt ), which prompts the users to enter the data that will be used to randomly generate their movie star name, and the complete state ( moviename_complete ), in which the generated result is presented. SAWA determines the application state, then passes the name of the style that reflects that state to AxKit, which performs the actual content transformation.

Let's take a quick look at the stylesheets themselves before moving on. First, Example 7-10 shows the style associated with the default, prompt state.

Example 7-10. prompt.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="params.xsl"/> <xsl:template match="/">   <xsl:apply-templates/> </xsl:template> <xsl:template match="application">   <application>   <xsl:apply-templates/>   <body>     <form name="prompt" action="{$request.uri}" method="post">       <div class="appmain">         <div class="row">           <span class="label">Name of classy hotel:</span>            <span class="inputw">               <input name="last.hotel" type="text" value="{$last.hotel}"/>           </span>         </div>         <div class="row">           <span class="label">            Name a street you lived on when you were a teenager:</span>            <span class="inputw">               <input name="last.street" type="text" value="{$last.street}"/>           </span>         </div>         <div class="row">           <span class="label">Your mother's maiden name:</span>            <span class="inputw">               <input name="last.maiden" type="text" value="{$last.maiden}"/>           </span>         </div>         <div class="row">           <span class="label">Your middle name:</span>           <span class="inputw">               <input name="first.middle_name" type="text" value="{$first.middle_name}"/>           </span>         </div>         <div class="row">           <span class="label">The name of your favorite pet:</span>            <span class="inputw">               <input name="first.pet" type="text" value="{$first.pet}"/>           </span>         </div>         <div class="row">           <span class="label">Name of your favorite model of car:</span>            <span class="inputw">               <input name="first.car" type="text" value="{$first.car}"/>           </span>         </div>         <div style="padding-top: 10px; text-align: center; clear: both;">           <input name="complete" type="hidden" value="1"/>           <input type="submit" value="Generate Name"/>           <input type="reset" value="Start Over"/>         </div>       </div>     </form>   </body>   </application> </xsl:template> </xsl:stylesheet> 

As you may expect, this stylesheet does little more than create the data entry interface that visitors will use to type in the information that will be shuffled to select their movie star name. Example 7-11 shows the stylesheet associated with the complete application state.

Example 7-11. complete.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="params.xsl"/> <xsl:template match="/">   <xsl:apply-templates/> </xsl:template> <xsl:template match="application">   <application>   <xsl:apply-templates/>   <body>    <div>      <p>        Your new <i>nom d'cinema</i> is:         <b>          <xsl:value-of select="/application/first_name"/>          <xsl:text> </xsl:text>          <xsl:value-of select="/application/last_name"/>        </b>      </p>     </div>     <form name="prompt" action="{$request.uri}" method="post">       <input name="last.hotel" type="hidden" value="{$last.hotel}"/>       <input name="last.street" type="hidden" value="{$last.street}"/>       <input name="last.maiden" type="hidden" value="{$last.maiden}"/>       <input name="first.middle_name" type="hidden" value="{$first.middle_name}"/>       <input name="first.pet" type="hidden" value="{$first.pet}"/>       <input name="first.car" type="hidden" value="{$first.car}"/>       <input name="complete" type="hidden" value="1"/>       <div>         <input type="submit" value="Regenerate"/>         <input type="button" value="Start Over" onClick="location='{$request.uri}'"/>       </div>     </form>   </body>   </application> </xsl:template> </xsl:stylesheet> 

This stylesheet presents the randomly selected movie star name (passed through in the XML returned from the SAWA output class) along with a hidden form that allows the user to regenerate a new name using the same set of input data. Also, each of the state-specific stylesheets imports the params.xsl stylesheet. (See Example 7-12.) This is really just a tiny stylesheet fragment that allows the other stylesheets to access the CGI and other request parameters needed for the two HTML forms.

Example 7-12. params.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <!-- Global Params --> <xsl:param name="request.uri"/> <xsl:param name="last.hotel"/> <xsl:param name="last.street"/> <xsl:param name="last.maiden"/> <xsl:param name="first.middle_name"/> <xsl:param name="first.pet"/> <xsl:param name="first.car"/> </xsl:stylesheet> 

From the earlier configuration, note that the two state-specific stylesheets are first in their respective AxKit style processing chains and that both chains pass their content through to a final common_html.xsl stylesheet. (See Example 7-13.) This small stylesheet simply passes through the result of the previous XML to HTML transformations, while adding the global header and footer common to both application states.

Example 7-13. common_html.xsl
 <![CDATA[ <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html"/> <xsl:template match="/"> <html>   <head>     <title>Movie Star Name Generator</title>   </head>   <xsl:apply-templates select="/application/body"/> </html> </xsl:template> <xsl:template match="body"> <body>   <div class="pagehead">     Movie Star Name Generator   </div>   <div class="message">     <p>       <xsl:apply-templates select="//message"/>     </p>   </div>   <xsl:apply-templates select="./*"/>   <div class="legal">     Copyright 1994-2003 WebCliche, Inc. All rights reserved.   </div> </body> </xsl:template>   <!-- copy the HTML from the previous transformation verbatim -->   <xsl:template match="node( )@*">     <xsl:copy>       <xsl:apply-templates select="@*node( )" />     </xsl:copy>   </xsl:template>    </xsl:stylesheet> 

That's all there is to it. A couple of tiny Perl classes, a few small XSLT stylesheets, and the application is complete. Let's look at the result. Chapter 7 shows the prompt. Figure 7-2 shows the completed result.

Figure 7-1. Movie star name generator: state two (prompt)

Figure 7-2. Movie star name generator: state two (complete)

You may wonder why you might combine both SAWA and AxKit in the same application, given that each offers features that the other possesses. It's really a personal choice. Having used both extensively, for me, the combination is a natural fit. SAWA's simple modularity and Perl's pure environment are well suited for generating XML content. This, plus AxKit's flexible styling options and built-in caching facilities, add up to a winning combination. In any case, whether you favor XSP, application ContentProviders, aggregate data URIs, or a combination of all three, Axkit lets developers choose the tools and techniques that suit them best.

XML Publishing with AxKit
XML Publishing with Axkit
ISBN: 0596002165
EAN: 2147483647
Year: 2003
Pages: 109
Authors: Kip Hampton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net