Dynamically Choosing Style Transformations

The options that you’ve looked at so far for associating documents with transformative processes are quite capable. Often, the use of stylesheet processing instructions, or simple AxKit processor definitions, combined with constraints imposed by Apache built-in <Files>, <Directory>, <Location>, and similar block-level directives, are all you need to meet the needs of many sites. However, AxKit offers even more flexibility by providing additional mechanisms that allow you to combine these low-level style processing options into logical groups that can be selected at runtime. In this section, I introduce the concepts and syntax for creating named styles and media types and explain how these can be used in conjunction with the StyleChooser and MediaChooser modules to apply just the right content transformations under the right circumstances.

The reasons for using named style and media blocks are quite varied. Generally, they are best suited for cases when you need to select a transformation (or a chain of transformations) based on a condition external to the properties of the source XML content itself. Some reasons to use named styles and media blocks include:

Vendor branding

Your site offers a service, and each customer wants the content presented in a way that matches his unique look and feel.

User-selected skinning

One size never fits all. You want to offer your visitors the ability to select the style that suits them best.

Automated metadata extraction

You want to extract important metadata, like abstract summaries, author, title, copyright information, etc., by simply applying an alternative set of styles to your documents.

Role-specific data transformations

Your customers, vendors, shipping department, and company president all have different needs when they look at your product list. You want to serve all of them from the same XML data source.

Application state-specific transformations

You want to apply different transformations to your XML data to reflect the current state (or screen) of your online applications.

Any or all of the above

You want to incorporate any or all of the features listed, plus the ability to provide each option across a series of different client devices (phone, desktop browser, TV, etc.).

AxKit’s named styles and media blocks, in conjunction with StyleChooser and MediaChooser plug-in modules, provide absolute control over when and how your XML content gets transformed. Literally, any condition that can be determined via the Perl programming language can be used to regulate which style processors will be applied to your content. The next two sections examine both named styles and named media, how they can be used together, and how AxKit’s plug-ins can be used to select just the right combination.

Named Styles and StyleChoosers

A named style consists of one or more style processor definitions grouped together and given a unique name. The name given is used in conjunction with a StyleChooser or other AxKit plug-in to select the processors to apply in response to a given request. These modules set AxKit’s internal preferred_style property based on a condition; then, if the name contained in that property matches the name given a named style block within the scope of the current request, all styles associated with that name are applied to the source document.

Named styles are created in one of two ways: by using the <AxStyleName> configuration directive or by adding the optional title and alternate attributes to an xml-stylesheet processing instruction in the source XML document. When using directive-based named styles, the <AxStyleName> configuration block acts as a container of one or more styling directives and uniquely associates that set of processors with the given name.

Suppose that you have an XML document that contains detailed data about the list of products offered by an online shop. You want to present a page typical for this kind of application: a list of all products, and links to more detailed information for each. Now, you could use an offline transformation to carve up the larger document into a set of smaller ones, then associate different style processors based on the files’ location on the disk—one for the list view, and one for the detail views. The weakness of that approach, however, is that you must remember to rerun the process every time the product list is updated. AxKit’s named styles provide another option: you can create two named styles that can be applied to the same global list of products—one presents the full list with minimal detail, and one reveals additional details for a specific product. The directives used to set up these named alternate views may look like this:

<Files products.xml>
  <AxStyleName 
                   list_view
    AxAddProcessor text/xsl /styles/list.xsl
  </AxStyleName>

  <AxStyleName detail_view
    AxAddProcessor text/xsl /styles/detail.xsl
  </AxStyleName>
</Files>

This creates two named styles that AxKit can apply conditionally when serving the products.xml document: the list_view style, which transforms the content into the complete list of products, and the detail_view style, which selects a single record from the global list and shows more detailed information about that specific item. The same can be achieved when using xml-stylesheet processing instructions by including title and alternate attributes with the appropriate values:

<?xml-stylesheet type="text/xsl" href="/styles/list.xsl" 
                    title="list_view"  alternate="yes"?>
<?xml-stylesheet type="text/xsl" href="/styles/detail.xsl" 
title="detail_view" alternate="yes"?>

You can now use a StyleChooser plug-in to select between the two styles. AxKit ships with a number of default StyleChooser modules that set the preferred style based on commonly used conditions such as the name of the requesting user agent, a specific query string parameter, or the value of an HTTP cookie. These modules are added to AxKit via the AxAddPlugin configuration directive. So, for example, if you want to choose between the list view and detail view of your products document, based on the query string sent by the browser, you add the following to your configuration file:

 AxAddPlugin Apache::AxKit::StyleChooser::QueryString

With this in place, you can now easily select the different views of your products document by adding a style parameter whose value matches one of your named styles to the query string of a request to products.xml:

http://myhost.tld/products.xml?style=detail_view

When using named styles, it is important to set up a default style to cover cases in which the StyleChoosers may not set a preferred style or in which the style name returned does not match any style configured for the current document. This can be done by adding a style with the name #default or by using the AxStyle directive to set an existing named style as the default style:

# Fall back to a default style that does not match any
# other named style
<AxStyleName 
                    "#default" 
   AxAddProcessor test/xsl /styles/list.xsl
</AxStyleName>

# Does the same, but uses the existing 'list_view' style
# as the default style.
<AxStyleName 
                    list_view
  AxAddProcessor test/xsl /styles/list.xsl
 </AxStyleName>

AxStyle list_view

You can achieve the same using xml-stylesheet processing instructions by defining a preferred style (one that has a title attribute but not an alternate attribute with the value of yes):

<?xml-stylesheet type="text/xsl" href="/styles/product_list.xsl" 
title="product_list"?>
<?xml-stylesheet type="text/xsl" href="/styles/product_detail.xsl" 
title="product_detail" alternate="yes"?>

Another common use for named styles is user-defined “skinning” of a site’s content. Recall your cryptozoology site from Chapter 3. Imagine that this site has become the primary resource on the Web for that topic. Both serious researchers in the area, as well as the hyper-ironic, slumming for a kitschy thrill, frequent it. Both groups share an interest in the site’s content, but their expectations and preferences about how that content is best presented are likely to be quite different. To meet this need, you can create a new, flashier stylesheet to render the content for your hipster visitors and then add a named style to your configuration file that reflects that preference:

# the existing, plain style
<AxStyleName plain>
  AxAddProcessor text/xsl stylesheets/cryptozoo.xsl
</AxStyleName>

# provides the RSS 1.0 news feed
# view of the sightings
<AxStyleName rss1>
  AxAddProcessor text/xsl stylesheets/cryptidsightings_rss.xsl
</AxStyleName>

# the new, flashy style
<AxStyleName flashy>
  AxAddProcessor text/xsl stylesheets/cryptozoo_flashy.xsl
</AxStyleName>

# set the plain style as default
AxStyle plain

# lets the ?style=rss1 interface work for the news feed
AxAddPlugin Apache::AxKit::StyleChooser::QueryString

Here, the choice between styles is not so much a short-term, functional one (as in the list/detail view for your products document) but rather a persistent preference that should be reflected each time the reader visits the site. In this case, you are better off giving the user an HTTP cookie and using AxKit’s Cookie StyleChooser.

But wait, you are already using the query string StyleChooser to provide the interface to the RSS feed. You don’t want that link to break. No matter. Since AxKit plug-ins are executed in the order that they appear in the configuration, you only need to add the Cookie StyleChooser before the one that examines the query string. That way, even if the user has a cookie that sets AxKit’s preferred_style, its value will be overwritten by the query string StyleChooser. Therefore, the functional transformation that provides the RSS interface will continue to work:

# lets the ?style=rss1 interface work for the news feed
# and allows skinning based on the user's cookie
AxAddPlugin Apache::AxKit::StyleChooser::Cookie
AxAddPlugin Apache::AxKit::StyleChooser::QueryString

What if you decide not to rely solely on AxKit’s order of execution to handle the cases in which the styles may overlap? You can choose instead to write a little plug-in module that combines the two StyleChoosers while giving RSS query string interface top priority. (See Example 4-1.)

Example 4-1. CryptidStyleChooser.pm

package CryptidStyleChooser;

# Combine the Cookie and QueryString StyleChoosers, giving
# preference to a style named 'rss1' in the query string.

use strict;
use Apache::Constants qw(OK);
use Apache::Cookie;

sub handler {
    my $r = shift;

    my $preferred_style = undef;

    # Borrow the key names for the existing StyleChoosers
    my $query_key  = $r->dir_config('AxStyleChooserQueryStringKey') || 'style';
    my $cookie_key = $r->dir_config('AxStyleChooserCookieKey') ||
                     'axkit_preferred_style';

    my %query_params = $r->args( );

    if ( defined ($query_params{$query_key} ) ) {
        $style = $query_params{$query_key});

        # give preference to the rss1 query param by returning if you find it set.
        if ( $style eq 'rss1' ) {
            $r->notes('preferred_style', $style);
            return OK;
        }
    }

    # let the users' cookie override the query string otherwise
    my $cookies = Apache::Cookie->fetch; . .
    if ( defined $cookies->{$cookie_key} ) {
        $style = $cookies->{$cookie_key}->value);
    }

    # finally, set AxKit's internal 'preferred_style' if you found a style
    if ( defined( $style ) ) {
        $r->notes('preferred_style', $style;
    }
    return OK;
}

1;

To use your custom StyleChooser plug-in, you need to install it on your server in a location that Perl knows about and to replace the former AxAddPlugin directives with:

# lets the ?style=rss1 interface work for the news feed
# and allows skinning based on the user's cookie
AxAddPlugin CryptidStyleChooser

You can now rest assured that the correct stylesheets will be applied to the documents in your cryptid site for each case that arises. In reality, custom StyleChooser plug-ins are rarely required. (In fact, you didn’t really need one here.) By creating a StyleChooser that covers both the presentational and functional transformations for your site, you peeked at just how easy it can be to use named styles to get AxKit to apply exactly the styles that you want, no matter how tricky the requirements first appear.

AxMediaType and MediaChoosers

In addition to named styles, AxKit offers a way to further define the styles that are conditionally applied to a site’s resources by associating sets of styling directives with the media type the requesting client expects. This is achieved using the <AxMediaType> container directive.

<AxMediaType screen>
  AxAddProcessor text/xsl /styles/screen.xsl
</AxMediaType>

Similar to the <AxStyleName> block, the <AxMediaType> tells AxKit to examine an internal property, preferred_media , set by a MediaChooser, or other plug-in. If the value of that property matches the name given to an <AxMediaType> block, the directives within that block are used to process the current request:

# Set a global style for most client devices
<AxMediaType screen>
  AxAddProcessor text/xsl /styles/screen.xsl
</AxMediaType>

# But give smaller devices a different look
<AxMediaType handheld>
  AxAddProcessor text/xsl /styles/handheld.xsl
</AxMediaType>

Unlike named styles, AxKit sets a default value of screen for the preferred media type, if no other is found. This behavior can be altered by setting the AxMedia directive to the name of the <AxMediaType> block that you want to use as the default. For example, the following configures AxKit to use the handheld media type as the default within the current context:

AxMedia handheld

Pretend that you want to further enhance your silly cryptozoology site. You want to support visitors using web phones. In this case, you simply create another set of stylesheets that renders the content appropriately for that platform and alter your configuration to reflect the change:

# the previous, screen-only config, now wrapped in an <AxMediaType> block for clarity
<AxMediaType screen>
  <AxStyleName plain>
    AxAddProcessor text/xsl stylesheets/cryptozoo.xsl
  </AxStyleName>

  <AxStyleName rss1>
    AxAddProcessor text/xsl stylesheets/cryptidsightings_rss.xsl
  </AxStyleName>
  
  <AxStyleName flashy>
    AxAddProcessor text/xsl stylesheets/cryptozoo_flashy.xsl
  </AxStyleName>
</AxMediaType>

# the new <AxMediaType> for handheld support
<AxMediaType handheld>
  <AxStyleName plain>
    AxAddProcessor text/xsl stylesheets/cryptozoo_handheld.xsl
  </AxStyleName>
  
  <AxStyleName rss1>
    AxAddProcessor text/xsl stylesheets/cryptidsightings_rss.xsl
  </AxStyleName>
</AxMediaType>

AxStyle plain

You can give the same name to two different-named style blocks in the same configuration context without collision, so long as they appear as the children of different media type blocks. Here, you give the default style contained by the new media type the same name as the default screen style. This allows the AxStyle directive to work as expected for both media types:

# lets the ?style=rss1 interface work for the news feed
AxAddPlugin CryptidStyleChooser
AxAddPlugin Apache::AxKit::MediaChooser::WAPCheck

With this addition, your absurd little mystery animals’ site becomes quite respectable, technologically speaking. You offer users a choice of styles through which to view the content, an RSS 1.0 news feed of recent cryptid sightings, and full support for surfers using web phones—all this from two XML documents, a few stylesheets, and, most importantly, knowing how to configure AxKit to apply the right style at the right time.

Get XML Publishing with AxKit now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.