So far, we have examined how AxKit can be used to transform static XML documents. Transforming such documents via XSLT stylesheets or another means that can alter, create, or include content conditionally provides a certain level of dynamism, but most modern web sites include features or resources that need to change for each unique request. An altered version of a static source is not enough, the source itself must be generated programatically.
By now, you are surely aware of the value of separating content from presentation, and how that value increases when the documents being served are marked up using semantically rich grammar that intimately captures (or communicates) the meaning of the information they contain. The same principle holds true when creating XML-based applications—the key difference is that markup generated in an application context should attempt to most accurately reflect the structure and roles of the resources associated with (or required by) the current state of the application, rather than those of a static narrative document.
By generating only the data relevant to the current state of the application, developers working on the backend libraries have simpler and better defined targets to hit. Since the markup being generated does not include any presentational elements, the grammar for the document being produced is usually greatly simplified. Among other things, this means that the generated content can be evaluated in detail using nothing more than a validating XML parser and a Document Type Definition (for instance, one of the XML schema languages) to verify the result. Hence, acceptance testing can become fully deterministic—the coder knows her job is done when the generated grammar passes a validity test, not when it seems to render correctly in one or another client application.
Similarly, having a simplified, well-defined application grammar offers stylesheet authors the ability to develop and test their stylesheets using sample documents that accurately reflect the data that will be generated when the application goes into production. This means that application development can push ahead asynchronously—the stylesheets that will render the application content can be completed at the same time (or even before) the code that generates the content is written.
Also having the ability to reuse the same content to meet the needs and expectations of a variety of clients is a key benefit of an XML application environment. Being able to deliver the same essay as either HTML or PDF is a cool feature; being able to offer the same online application to a variety of clients can directly affect the profitability of a web-based enterprise.
In short, the benefits of separating content from interface are especially noticeable when the markup in question is being generated dynamically in the context of an online application. In this chapter, I introduce a few of the tools and techniques that AxKit offers for creating content dynamically and explain how applying transformative styles to that generated content can be used to create flexible, sophisticated online applications.
Originally created by the developers of AxKit’s sister Apache project, Cocoon, eXtensible Server Pages (XSP) offers an XML-centric implementation of the Server Pages model. Unlike XSLT or XPathScript, an XSP document is not a separate stylesheet that is applied to an XML document to transform it; rather, XSP is concerned solely with generating content. Literal markup is mixed with functional code and elements from the XSP grammar within a single document to create an XML instance dynamically when that document is passed through the XSP processor. In AxKit’s implementation, the embedded programming language is Perl.
As with AxKit’s other Language modules, support for
XSP must be explicitly enabled using the
AxAddStyleMap
configuration directive:
AxAddStyleMap application/x-xsp Apache::AxKit::Language::XSP
XSP diverges from the norm a bit in how documents are associated with
the XSP processor. Unlike XSLT or XPathScript, XSP is self-contained,
and there is no external stylesheet to apply. This means that when
setting up the style processing directives for XSP, you use the
literal string NULL
, whereas if you use another
Language module, you would typically put the path to the stylesheet.
# The familiar pattern, using XSLT AxAddProcessor text/xsl /styles/somestyle.xsl # In XSP there is no stylesheet, so the # string NULL is used instead AxAddProcessor application/x-xsp NULL # The same, but as a conditional processor, based # on the top-level element name and its namespace URI AxAddRootProcessor application/x-xsp NULL {http://apache.org/xsp/core/v1}page
XSP’s basic syntactic requirements are very simple:
every valid XSP document
must be a well-formed XML document, and all XSP elements must be
bound to the namespace URI
http://apache.org/xsp/core/v1
(binding that URI to
the prefix xsp
is conventional, but certainly not
required). In contrast to XPathScript, Active Server Pages, PHP, and
similar technologies that use special pseudo tags (such as
<% . . . %>
, for example) as delimiters to
separate code from literal markup, XSP uses an XML application
grammar that separates literal and processed output through the use
of specific XML elements bound to the XSP namespace. For example, the
xsp:logic
element creates a block that can contain
any block of free-form Perl code, while the
xsp:expr
element interpolates a single expression
into its literal result. The following shows a minimal, valid XSP
document that inserts the current time into the content via an
xsp:expr
element:
<?xml version="1.0"?> <application> <time> <xsp:expr xmlns:xsp="http://apache.org/xsp/core/v1">scalar localtime( )</xsp:expr>. </time> </application>
Although XSP elements and code can be embedded into any XML document
and passed to the XSP processor, an XSP page more commonly contains:
a
top-level xsp:page
element containing optional
xsp:structure
and xsp:logic
elements, and a required non-XSP element that becomes the top-level
element of the result generated by the XSP processor (this is often
called the user-root element). The following
illustrates this basic structure:
<?xml version="1.0"?> <!-- The top-level <xsp:page> element, bound to the XSP namespace URI --> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <!-- optional <xsp:structure> used to import external Perl modules for use in the current page--> <xsp:structure> <xsp:import>Some::Perl::Module</xsp:import> </xsp:structure> <!-- class-level logic (subroutines, global initialization, etc.) --> <xsp:logic> sub now { return scalar localtime( ); } </xsp:logic> <!-- The following <application> element is the user-root that will become the top-level element after processing --> <application> <time> <xsp:expr>now( )</xsp:expr> </time> </application> </xsp:page>
The following shows the result returned from both of the above examples:
<?xml version="1.0"?> <application> <time>Thu Feb 12 20:26:11 2004</time> </application>
Before we dig further into the details of XSP’s syntax, it is important to note that the most effective use of XSP extends the Server Pages model beyond the code-mixed-with-HTML mess that may have driven you to look for an alternative environment such as AxKit in the first place. Yes, XSP allows and even encourages the mixture of XML markup and Perl code, but this is not the same as having code hardwired to generate presentational markup (which is arguably the true weakness found in most uses of the Server Pages technologies, not simply the fact that code and markup appear in the same document). To get the maximum benefit from XSP and AxKit, it is best to make sure that the markup that your XSP pages generate conforms to a grammar that best reflects the semantics and state of the application, irrespective of how that content may be presented. As long as your XSP-based applications meet this criteria, the syntactic details of how that data may be generated become solely a matter of personal taste.
For many web developers, the XML-influenced approach to generating dynamic content requires a little bit of mental adjustment. One tends to think of generating pages or screens, rather than constructing semantically meaningful data structures. It helps to remember, though, that the result generated by the XSP processor is really the beginning of the processing chain, not the end. In most cases, at least one transformative style will be applied to the XML created, and it is the stylesheet’s job to transform the generated data into a usable interface. Therefore, you are free to generate simpler markup that reflects only the application’s state and data structures.
Now that this conceptual foundation is laid, let’s get to the business of introducing XSP. First, here is a summary of the grammar as a whole:
xsp:page
The optional top-level element for an XSP document. If present, it must be the top-level element of the document.
xsp:structure
A wrapper element for one or more xsp:import
elements. May appear only as a child of a top-level
xsp:page
element and before the opening tag of the
document’s user-root element.
xsp:import
The text content of this element is expected to contain the name of a
Perl class to be loaded during processing (similar to
Perl’s use
function). This
element may only appear as a child of the
xsp:structure
element.
xsp:logic
The content of this element is expected to be a free-form Perl block that is evaluated during processing.
xsp:expr
The content of this element is expected to be a Perl expression that is interpolated during processing. The result is added to the output as a literal string.
xsp:content
The content of this element is expected to be a well-formed XML
fragment and is added, as is, to the resulting output. Although it
may appear anywhere inside the user-root element, it most often
appears as the child of an xsp:logic
element, as a
convenient way to escape out of the code to generate markup.
xsp:element
Creates an XML element whose name is defined by the required
name
attribute.
xsp:comment
The contents of this element will be wrapped in an XML comment in the resulting output.
xsp:pi
Will be expanded into an XML processing instruction, whose target is
defined by the required target
attribute.
XSP’s grammar is quite small—just nine elements—but these elements, plus Perl’s conditional statements, loops, and other logical constructs, can be combined to create very sophisticated XML content.
First, it is important to keep in mind that all non-XSP elements, not otherwise skipped over based on conditional logic, are passed through the XSP processor verbatim into the generated result:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <application> <user-info/> </application> </xsp:page>
Running this through the XSP processor gives the following predictable result:
<application> <user-info/> </application>
Free-form Perl code can be embedded
any place inside an XSP document using the
xsp:logic
element. At the same time, single
elements or well-balanced chunks can be generated from within those
logic blocks by wrapping the output in an
xsp:content
element. Also, the
xsp:expr
element can be used to interpolate a
simple Perl scalar expression into a literal string:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <application> <user-info> <xsp:logic> my $connection = $r->connection; my $ip = $connection->remote_ip; <xsp:content> <ip-address><xsp:expr>$ip</xsp:expr></ip-address> </xsp:content> if ( $ip =~ /^192./ ) { <xsp:content> <is-local>true</is-local> </xsp:content> } else { <xsp:content> <is-local>false</is-local> </xsp:content> } </xsp:logic> </user-info> </application> </xsp:page>
In most cases, the XSP processor is smart enough to tell the
difference between Perl code and literal markup that is meant to be
part of the generated result. This makes the
xsp:content
element
optional. Therefore, you could trim the
previous example into the following and get the same result:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <application> <user-info> <xsp:logic> my $connection = $r->connection; my $ip = $connection->remote_ip; <ip-address><xsp:expr>$ip</xsp:expr></ip-address> if ( $ip =~ /^192./ ) { <is-local>true</is-local> } else { <is-local>false</is-local> } </xsp:logic> </user-info> </application> </xsp:page>
There are a few things to remember about
xsp:logic
elements.
First, any logic block that appears outside of
the user-root element is considered a special, global block that the
XSP processor only interprets once. This global
section is intended to provide a handy place to declare any
subroutines or constant variables that your XSP page may need, while
saving the overhead associated with reevaluating the code for every
request. This provides a performance boost, but the behavior of the
special, global case can sometimes snare the unwary. The following
two XSP pages exemplify a common trap:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <!-- logic block outside of the user-root --> <xsp:logic> my $time = scalar localtime( ); </xsp:logic> <application> <time><xsp:expr>$time</xsp:expr></time> </application> </xsp:page>
The $time
scalar variable is set in an
xsp:logic
block outside of the user-root
application
element—that is, in the special,
“global” section. Compare that with
this very similar example:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <application> <!-- the same block, but inside the user-root --> <xsp:logic> my $time = scalar localtime( ); </xsp:logic> <time><xsp:expr>$time</xsp:expr></time> </application> </xsp:page>
The two appear identical, but in the second example, the
$time
scalar is set in a logic block
within the user-root element. You may expect
that the two pages would return the same results, but remember that
any logic block that appears outside of the user-root element is a
special case that is only evaluated once. So the result returned from
the first page shows the same time string no matter how many times
the page is requested, while the second updates the time string for
each request.
However, it is perfectly safe to put subroutines in the global block.
In this case, it doesn’t matter that the code is
only evaluated once. The subroutine still
executes at request time. So if you
don’t want to put the xsp:logic
element inside the user-root, the following still works as
you’d expect:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <!-- logic block outside of the user-root --> <xsp:logic> sub now { return scalar localtime( ); } </xsp:logic> <application> <time><xsp:expr>now( )</xsp:expr></time> </application> </xsp:page>
The second thing to keep in mind when creating inline
xsp:logic
blocks concerns possible syntactic
conflicts between Perl and XML. Certain common Perl expressions can
cause XML well-formedness errors unless they are properly escaped.
The specific characters you have to watch out for are
«&
» and
«<
».
<xsp:logic> # Parser sees '<' and thinks it's the beginning of a new XML element if ( $this_number < $that_number ) { } my $here_doc = <<"TARGET"; # Parser sees '&' and thinks it's the beginning of an XML entity reference if ( $this_condition && $that_condition ) { } my $val = &sone_function( ); </logic>
The preferred solution here is to wrap the contents of the
xsp:logic
element that contain the conflicting
characters in a CDATA (character data) section that the XML parser
passes over without trying to parse it:
<xsp:logic><![CDATA[ # You are now free to use & and < to your heart's content ]]><xsp:logic>
Using a CDATA section to tell the XML parser to skip the contents of the logic block also tells the parser to ignore any XSP elements or literal output, as well, so be sure to break out of the CDATA if you want to return content.
New XML elements may also be generated through use of the
xsp:element
element. The string passed to this
element’s required name
attribute
becomes the name of the newly generated element. The value set for
the name
attribute is usually a literal string,
but you can also pass in a Perl scalar expression by wrapping it in
curly braces. This is similar to XSLT’s attribute
value templates and offers the ability to produce elements whose
names are generated programmatically at request time.
<params> <xsp:logic> my %query_params = $r->args; foreach my $key ( keys( %query_params )) { <xsp:element name="{$key}"><xsp:expr>$query_params{$key}</xsp:expr></xsp:element> } </xsp:logic> </params>
Similarly, XML attributes can be generated via the
xsp:attribute
element. This
element’s required name
attribute
becomes the name of the newly created attribute, while the contents
of this element become the attribute’s value.
<params> <xsp:logic> my %query_params = $r->args; foreach my $key ( keys( %query_params )) { <param> <xsp:attribute name="name"><xsp:expr>$key</xsp:expr></xsp:attribute> <xsp:attribute name="value"><xsp:expr>$query_params{$key}</xsp:expr></xsp:attribute> </param> } </xsp:logic> </params>
Finally, comments and processing instructions can be generated using
the xsp:comment
and xsp:pi
elements, respectively. Generating comments can be especially useful
when debugging your XSP while it is still in development. Rather than
dumping information to the host’s error log, you can
simply produce a comment for nonfatal but unexpected errors, and the
result appears directly in the output in a way
that’s easily distinguished, visibly, from the rest
of the content:
<xsp:logic> if ($var eq 'this' ) { <this><xsp:expr>$var</xsp:expr></this> } else { <xsp:comment>Was expecting $var to be 'this' but got <xsp:expr>$var</xsp:expr > instead.</xsp:comment> } </xsp:logic>
Perl’s popularity as a programming language has as
much to do with the army of useful, open source extension modules
freely available from the Comprehensive Perl
Archive Network (CPAN) as it does with any particular
feature of the language itself. Once installed (details about
installing modules from CPAN can be found by running
perldoc
CPAN at a shell prompt
on any machine on which Perl is installed), these modules can be used
from within your XSP pages by adding an
xsp:structure
element as a direct child of the
top-level xsp:page
element, then adding an
xsp:import
element containing the name of the Perl
package for each module that you want to use in your page:
<xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"> <xsp:structure><xsp:import>Geo::IP</xsp:import>
</xsp:structure> <application> <user-info> <xsp:logic> my $connection = $r->connection; my $ip = $connection->remote_ip; my $mapper = Geo::IP->new( ); <ip-address><xsp:expr>$ip</xsp:expr></ip-address> <country><xsp:expr>$mapper->country_name_by_addr($ip)
</xsp:expr></country> </xsp:logic> </user-info> </application> </xsp:page>
You should now have a pretty good idea about XSP’s page-level syntax and structure. There’s more to the story, though. From what you have learned so far, you can generate application content quickly, but each page exists only unto itself, and the solutions are not reusable. Fortunately, AxKit’s XSP implementation allows you to extend this basic framework to include your own custom application grammar extensions that can be reused in any number of pages to meet a variety of needs. These extensions are called tag libraries.
XSP tag libraries (or taglibs, for short) provide a means to extend XSP’s functionality by mapping XML elements in a specific, custom XML grammar to Perl code that expands and replaces those elements with other markup generated dynamically, performs a programmatic task behind the scenes, or most often, both. A tag library’s elements are distinguished from literal output and elements in the XSP core grammar by binding those elements to a specific XML namespace. The following shows a simple XSP page that employs the Util tag library (available on CPAN) to return the current time:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1"xmlns:util="http://apache.org/xsp/util/v1"
<application> <time><util:time/>
</time> </application> </xsp:page>
XSP taglibs can be implemented in one of two ways: as a special XSLT, XPathScript, or other stylesheet (called a logicsheet) used to preprocess the content, expanding the elements in the taglib grammar into the XSP elements and Perl codes that implement the taglib’s behavior before the document is passed on the XSP processor, or more commonly, as a special Perl module registered with AxKit and used to extend the functionality of the XSP processor itself. You learn how to create both types of taglibs later in this chapter. For now, let’s focus on some existing module-based taglibs and how they can be used to extend your XSP pages. The following explains just a few of the more popular or interesting XSP taglibs available for use in your XSP applications:
A complete Wiki application implemented in XSP. Uses POD as the page syntax.
A general-purpose utility class that provides access to request and server data. Among other things, you can get or set HTTP headers, issue client redirects, examine whether the current request is using secure HTTP connections, and much more.
Send email using this simple XSP interface to Perl’s popular Mail::Sendmail module.
Feature-rich taglib for selecting data from any relational database supported by Perl’s DBI.
Offers several utility functions for including content from local files, remote URI, and interpolated expressions, as well as the ability to get the current date and time in a variety of formats.
An easy-to-use XSP interface for retrieving data from LDAP servers.
Robust XSP helper class for creating and validating data entry applications.
XSP interface to the popular site search/indexing tool, Swish-e.
There are many benefits of using XSP taglibs, but the most important is that they provide XSP developers with the ability to create generic, reusable libraries that implement common features or solve common problems.
Installing module-based XSP taglibs is exactly the same as installing any Perl module. If the taglib you want to install is available from CPAN, installation can be achieved quickly and painlessly using the CPAN shell that installs with Perl itself. Simply ask your systems administrator to install the taglib. (Be sure to provide her with the exact name of the taglib module you wish to install.) Or if you have permission, become root (superuser) and enter the following at a shell prompt:
perl -MCPAN -e shell install Some::XSP::Taglib
Once the module is installed, you must first register the taglib with
AxKit before you can start using it with your XSP applications. This
is achieved by adding an AxAddXSPTaglib
configuration directive to your httpd.conf
or
other Apache configuration file and passing the Perl package name of
the taglib module as the sole argument. The following registers the
Param and ESQL taglibs with AxKit:
AxAddXSPTaglib AxKit::XSP::Param AxAddXSPTaglib AxKit::XSP::ESQL
Adding the taglib modules in this way does two things: it configures
AxKit to load the module code (similar to Perl’s
built-in use
statement), and it registers the
unique XML namespace associated with that taglib grammar so that the
XSP processor knows to dispatch the processing of elements in that
namespace to the taglib’s Perl package and not to
simply pass them through as literal content.
Once a taglib module is registered, you may use the elements from its grammar in any XSP page. You need only to make sure to bind those elements to the XML namespace URI associated with that tag library; the XSP processor handles the rest.
The XSP taglib modules are already available from CPAN and cover an interesting set of cases. They are fantastic for adding reusable features to your own applications with a minimum of effort. However, there will surely be cases when what’s out there does not meet your needs, and you should implement your own taglibs. Now that you have an idea about what XSP taglibs are and how they are used, let’s examine how to create your own custom tag libraries.
Module-based XSP taglibs (which we will examine shortly) are by far more common, but we would be remiss not to touch on the alternative, logicsheet taglibs, first. As I mentioned earlier, a logicsheet taglib is an XSLT, XPathScript, or other stylesheet applied to the source XSP document before it is handed to the XSP processor. During this transformation, elements in the tag library’s grammar are expanded and replaced by the core XSP elements and Perl code that are required to implement the taglib’s behavior.
Suppose that while designing our next XSP application, you discover
that certain bits of environmental data are needed throughout each
stage in the application. You decide to encapsulate access to that
information in a tag library rather than duplicate the code in each
XSP document. Specifically, you need to access the
server’s environment variable
(%ENV
), you need to know whether the current user
has logged in (based on the presence of an HTTP cookie), and you want
a list of links to simplify the creation of the ubiquitous
“breadcrumb” navigation bar. Given
these requirements, you could define your taglib grammar in the
following way:
The XML namespace URI for this grammar is
http://localhost/xsp/myapp
, and the preferred namespace prefix ismyapp
.
myapp:env
This element is replaced by an env
element
containing a list of field
elements, each
containing name
and value
elements that reflect the name and value of each field in the
%ENV
hash.
myapp:breadcrumb
This element is replaced by a breadcrumb
element
that contains an ordered list of link
elements.
Each link element represents a link in the breadcrumb chain and
contains both an href
attribute containing the URI
of that step and a title
attribute that may be
used when creating a hyperlink for that step.
myapp:logincheck
This element is replaced by a logged-in
element
whose text contents are either true
or
false
depending on the presence or absence of an
HTTP cookie. If the value of the logged-in
element
is true
, a username
element
containing the current user’s name will also appear.
An XSP page using this taglib grammar may look something like Example 7-1.
Example 7-1. minimal_myapp.xsp
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1" xmlns:myapp="http://localhost/myapp"> <xsp:structure> <xsp:import>Apache::Cookie</xsp:import> </xsp:structure> <application> <meta> <myapp:breadcrumb/> <myapp:env/> <user> <myapp:logincheck/> </user> </meta> </application> </xsp:page>
Now you must create the logicsheet that expands the elements in the MyApp grammar into the XSP elements and Perl code that actually make the taglib work as you expect. Example 7-2 shows one possible way to do this, using XSLT to implement the logicsheet.
Example 7-2. myapp.xsl : the MyApp taglib as a preprocessed XSLT logicsheet
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsp="http://apache.org/xsp/core/v1" xmlns:myapp="http://localhost/xsp/myapp" > <xsl:template match="myapp:env"> <env> <xsp:logic> foreach my $key (keys(%ENV)) { <field> <name><xsp:expr>$key</xsp:expr></name> <value><xsp:expr>$ENV{$key}</xsp:expr></value> </field> } </xsp:logic> </env> </xsl:template> <xsl:template match="myapp:breadcrumb"> <breadcrumb> <xsp:logic> my $path ='/'; <link title="home" href="/"/> my @steps = split '/', $r->uri; for ( my $i = 0; @steps > $i; $i++ ) { my $step = $steps[$i]; next unless length $step; $path .= $step; $path .= '/' unless $i = = $#steps; <link> <xsp:attribute name="title"> <xsp:expr>$step</xsp:expr> </xsp:attribute> <xsp:attribute name="href"> <xsp:expr>$path</xsp:expr> </xsp:attribute> </link> } </xsp:logic> </breadcrumb> </xsl:template> <xsl:template match="myapp:logincheck"> <xsp:logic> my $cookie = Apache::Cookie->fetch; if ( defined( $cookie->{'username'} )) { my $username = $cookie->{'username'}->value; <logged-in>true</logged-in> <username><xsp:expr>$username</xsp:expr></username> } else { <logged-in>false</logged-in> } </xsp:logic> </xsl:template> <xsl:template match="*"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates /> </xsl:copy> </xsl:template>
This logicsheet taglib is really just a plain XSLT stylesheet that
replaces the elements in the MyApp grammar with the code, XSP
elements and literal markup required to implement the taglib.
Obviously, for this to work, you must apply the
myapp.xsl
stylesheet to the source document:
<FIles minimal_myapp.xsp> AxAddProcessor text/xsl /styles/myapp.xsl </Files>
With this snippet added to your configuration file, a request for the
minimal_myapp.xsp
document gives the following
result:
<?xml version="1.0"?> <xsp:page xmlns:xsp="http://apache.org/xsp/core/v1" xmlns:myapp="http://localhost/myapp"> <xsp:structure> <xsp:import>Apache::Cookie</xsp:import> </xsp:structure> <application> <meta> <breadcrumb><xsp:logic> my $path ='/'; <link title="home" href="/"/> my @steps = split '/', $r->uri; for ( my $i = 0; @steps > $i; $i++ ) { my $step = $steps[$i]; next unless length $step; $path .= $step; $path .= '/' unless $i = = $#steps; <link> <xsp:attribute name="title"> <xsp:expr>$step</xsp:expr> </xsp:attribute> <xsp:attribute name="href"> <xsp:expr>$path</xsp:expr> </xsp:attribute> </link> } </xsp:logic></breadcrumb> <env><xsp:logic> foreach my $key (keys(%ENV)) { <field> <name><xsp:expr>$key</xsp:expr></name> <value><xsp:expr>$ENV{$key}</xsp:expr></value> </field> } </xsp:logic></env> <user> <xsp:logic> my $cookie = Apache::Cookie->fetch; if ( defined( $cookie->{'myapp_username'} )) { my $username = $cookie->{'myapp_username'}->value; <logged-in>true</logged-in> <username><xsp:expr>$username</xsp:expr></username> } else { <logged-in>false</logged-in> } </xsp:logic> </user> </meta> </application> </xsp:page>
As expected, the elements from the MyApp grammar are replaced and all other content is copied through verbatim. You only need to configure AxKit to apply the XSP processor to this intermediate step to get the final result:
<Files minimal_myapp.xsp> # Preprocess using the XSLT logicsheet AxAddProcessor text/xsl /styles/myapp.xsl # And send the result to the XSP processor AxAddProcessor application/x-xsp NULL </Files>
This simple two-step processing chain delivers the final, desired result:
<?xml version="1.0" encoding="UTF-8"?> <application> <meta> <breadcrumb> <link title="home" href="/"/> <link title="axkitbook" href="/axkitbook/"/> <link title="samples" href="/axkitbook/samples/"/> <link title="chapt07" href="/axkitbook/samples/chapt07/"/> <link title="logicsheet.xml" href="/axkitbook/samples/chapt07/minimal_myapp"/> </breadcrumb> <env> <field> <name>PATH_INFO</name> <value/> </field> <field> <name>PATH</name> <value>/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/kip/bin</value> </field> <field> <name>GATEWAY_INTERFACE</name> <value>CGI-Perl/1.1</value> </field> <field> <name>MOD_PERL</name> <value>mod_perl/1.29</value> </field> </env> <user> <logged-in>true</logged-in> <username>ubu</username> </user> </meta> </application>
If this were a real-world XSP page, you would certainly include more
that just the bits of metadata that you have here. Also, you would
probably have a content
or
state
element that contains the data associated
with the current state of whatever application you were implementing,
but this example shows enough of the basics to get you up and
running.
Logicsheet-based taglibs such as the one you just created can be handy, but in practice, most XSP tag libraries are implemented as Perl modules that are then registered with the XSP processor. There are several benefits to using module-based taglibs rather than logicsheets. First, module-based taglibs do not require the XSP source to be preprocessed before execution; this not only saves the overhead associated with adding an additional transformation, but it often greatly simplifies the style processor configuration needed to serve the XSP pages throughout a given site. Also, module-based taglibs offer additional performance, since the Perl code that implements the taglib functions will be cached in memory, so the XSP processor is able to dispatch the handling of the taglib elements to the external module as quickly as it processes the elements in the core XSP grammar.
While it is possible to write taglib modules that interact directly with the low-level components of AxKit’s XSP engine, this approach is repetitive and error-prone, and requires intimate knowledge of the XSP processor’s internals. It is usually better to use one of the helper modules available to streamline and simplify the process of writing custom tag libraries. In Example 7-3, the MyApp grammar from the logicsheet sample is reimplemented as a Perl module using Steve Willer’s Apache::AxKit::XSP::TaglibHelper module that ships with the core AxKit distribution.
Example 7-3. MyApp.pm
package TaglibHelper::MyApp; use strict; use Apache::AxKit::Language::XSP::TaglibHelper; use Apache::Cookie; use vars qw( @ISA $NS @EXPORT_TAGLIB ); @ISA = qw( Apache::AxKit::Language::XSP::TaglibHelper ); $NS = 'http://localhost/xsp/myapp'; @EXPORT_TAGLIB = ( 'logincheck( )', 'env( ):listtag=env:itemtag=field', 'breadcrumb( ):as_xml=1', ); sub logincheck { my $cookie = Apache::Cookie->fetch; my $out = { }; if ( defined( $cookie->{'username'} )) { my $username = $cookie->{'username'}->value; $out->{'logged-in'} = 'true'; $out->{username} = $username; } else { $out->{'logged-in'} = 'false'; } return $out; } sub env { my @out = ( ); foreach ( keys( %ENV ) ) { push @out, { name => $_, value => $ENV{$_}}; } return @out; } sub breadcrumb { my $out = '<breadcrumb>'; $out .= '<link title="home" href="/"/>'; my $path ='/'; my $r = AxKit::Apache->request( ); my @steps = split '/', $r->uri; for ( my $i = 0; @steps > $i; $i++ ) { my $step = $steps[$i]; next unless length $step; $out .= qq|<link title="$step" href="$path"/>|; } $out .= '</breadcrumb>'; return $out; } 1;
There are several things to note here. First, the module is a subclass of the TaglibHelper class itself. This allows taglib module authors to implement only the functions required to react to the tags in that specific taglib while hiding the tedious low-level processing. Next, TaglibHelper works by allowing subclass authors to write simple Perl subroutines whose names match the local name of the elements in the taglib’s grammar. Each time a given element from the taglib being implemented is encountered by the XSP processor, the matching subroutine is called.
The arguments passed to the module’s subroutines and
the way the values returned from those subroutines will be processed
are determined by the function specifications passed to the
@EXPORT_TAGLIB
array. Each element in this array
takes the form of a string that follows the pattern illustrated here:
tagname([argument specification])[: additional options ]
While tagname
is the local name (unprefixed) of
the taglib element to match, argument
specifications
is an optional comma-separated list
of any child elements of the taglib element that should be passed as
arguments to the taglib subroutine, and additional
options
provide additional information about how
the data returned from the taglib subroutine is processed and
included in the result of the XSP process. Look back at the
@EXPORT_TAGLIB
array for your MyApp taglib module:
@EXPORT_TAGLIB = ( 'logincheck( )', 'env( ):listtag=env:itemtag=field', 'breadcrumb( ):as_xml=1', );
This tells the TaglibHelper parent class to look for three elements
from the taglib’s namespace,
logincheck
, env
, and
breadcrumb
. It further indicates that the
logincheck
subroutine expects no arguments (note
the empty argument specification) and that the subroutine returns a
simple Perl data structure that should be serialized to XML. The
specification for the env
subroutine shows that it
too expects no arguments, that it will return a list reference whose
entries should be named field
(itemtag=field
), and that the entire list should
be wrapped in an env
element
(listtag=env
). Finally, the specification for the
breadcrumb
indicates that it also expects no
arguments and that the result returned is a well-balanced chunk of
XML that should be parsed and included in the final result
(as_xml=1
).
With this module installed, you only need to register it by name via
the AxAddXSPTaglib
directive. You can begin using
the tags from the MyApp grammar in your XSP pages without having to
preprocess them using the logicsheet you created earlier.
# Load and register the taglib module AxAddXSPTaglib TaglibHelper::MyApp <Files minimal_myapp.xsp> # No need to preprocess, just send the source directly to the XSP processor AxAddProcessor application/x-xsp NULL </Files>
The result returned from a request to
minimal_myapp.xsp
using your new taglib module
is exactly the same as the result returned for the previous
logicsheet taglib, so I will not duplicate the output here.
In the same way that Perl’s many extension modules increase the power and value of the language as a whole by providing out-of-the-box solutions for common tasks, the judicious use of XSP tag libraries can add significant value to your XSP applications by reducing common application features to a handful of editor-friendly XML elements. If you are serious about building XSP applications with AxKit, I strongly suggest spending the time to learn the ins and outs of the TaglibHelper class or one of the other similar classes (SimpleTaglib or ObjectTaglib) that seek to streamline the process of writing custom XSP tag libraries—the time spent will more than pay for itself in the long run.
When you are first starting out with XSP, it can be hard to track down precisely where something went wrong with your page or taglib code. The reason is that, to make XSP pages fast, flexible, and cache-friendly, AxKit’s XSP engine is quite complex. (It’s actually a SAX Handler that dispatches events to the core event handlers and various taglib modules while dynamically constructing an intermediate Perl class that uses the DOM interface to generate the results!) A lot of advanced Perl magic happens behind the scenes. There are, however, several AxKit configuration directives that you can use to make debugging your XSP applications more straightforward.
First, be sure to set the
AxDebugLevel
directive
to maximum (10). This dumps copious amounts of information about what
AxKit is doing behind the scenes to your web host’s
error log. Next, be sure that the AxStackTrace
directive is set to On
. This causes the AxKit
error handling mechanism to produce a deep stack trace in the case of
a fatal exception.
Also, be sure to set up the AxErrorStylesheet
directive properly. As with the style processing directives, this
option accepts the MIME type associated with one of the Language
processing modules and the path to a stylesheet that will be applied
to the XML error document that AxKit produces if this directive is
present. For example, the following configures AxKit to apply the
/styles/axkit_error.xps
stylesheet to the
generated XML stack trace in the case of a fatal processing error:
AxErrorStylesheet application/x-xpathscript /styles/axkit_error.xps
This sends the transformed version of the Perl error and stack trace
to the requesting client instead of the disappointing default 500
Internal Server Error that Apache usually offers. This saves you
development time by obviating the need to look at the error log every
time something blows up. See the entry about the
AxErrorStylesheet
directive in
Appendix A for details about the XML that
AxKit produces in these cases.
Finally, AxKit offers the helpful
AxTraceIntermediate
directive explicitly to help debug XSP pages and complex processing
chains. This directive takes the path to a directory as its sole
argument. Then, when a document is requested, AxKit generates a
series of files in that directory (with the file extensions
0-n)—one for each transformation in the processing chain. If
the chain includes an XSP process, a special
.XSP
file is generated that shows the intermediate Perl
class that AxKit generates to implement the XSP page. This invaluable
tool can save hours of groping for subtle bugs in your XSP code. To
ensure that the results are easy to read, combine this directive with
the AxDebugTidy
On
directive to
format the intermediate files for better readability.
A word of caution, though—with the exception of the
AxErrorStylesheet
directive (which is good to use
in any case), each directive listed here has a negative impact on
AxKit’s performance and should, therefore, only be
used in a development environment, not on a production
server.
3.145.93.221