Style Processor Configuration Cheatsheet

So far, we have looked at the various individual elements that can be used to control how AxKit applies style transformations. So many, in fact, that seeing how these parts all fit together may be a bit tough. For example, a named style block (selected by a StyleChooser based on an environmental condition) may contain one or more AxAddDTDProcessor or similar conditional processing directives that are only applied if an additional condition is met. True AxKit mastery comes from knowing how to combine all its various configuration options to create elegant styling rules that meet the need of your specific application.

To help examine the processing order for various configuration combinations, we will create a series of very simple XSLT stylesheets whose sole purpose is to show the order in which AxKit applies a given style. The stylesheet in Example 4-2, alpha.xsl, simply appends the string . . . Alpha processed to the text of the top-level root element of the document being processed.

Example 4-2. alpha.xsl

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  version="1.0">

<xsl:template match="/">
  <root><xsl:value-of select="/*"/> . . . Alpha processed</root>
</xsl:template>

</xsl:stylesheet>

The tiny sample XML document used for the transformations is shown in Example 4-3.

Example 4-3. minimal.xml

<?xml version="1.0"?>
<root>Base content</root>

Add more stylesheets to these—beta.xsl, gamma.xsl, and so on—that do more or less the same thing—that is, adding . . . Beta processed, etc., to the text of the root element. Wherever a simple description does not suffice, use these stylesheets to examine the precise processing order based on the returned result.

Rule 1: Style Processors at the Same Lexical Level Are Evaluated in Configuration-File Order

Wherever more than one style processing directive exists within a given context, each will be added (or, in the case of conditional processors, evaluated) in the order in which they appear in the configuration files. In cases in which directives are added to the same context by both the httpd.conf file and an .htaccess file, those from the httpd.conf are added or evaluated first:

<Directory /www/mysite.com/>
   AxAddProcessor text/xsl /styles/alpha.xsl
   AxAddProcessor text/xsl /styles/beta.xsl   
</Directory>

As you may expect with this configuration, a document requested from root directory mysite.com will have the alpha.xsl stylesheet applied to the source content, then the beta.xsl stylesheet applied to the result of the first transformation:

<?xml version="1.0"?>
<root>Base content . . . Alpha processed . . . Beta processed</root>

Similarly, the rules governing conditional processing directives are tested in the order in which they appear in the configuration files:

<Directory /www/mysite.com/>
   AxAddProcessor text/xsl /styles/alpha.xsl
   AxAddRootProcessor text/xsl /styles/beta.xsl root
   AxAddProcessor text/xsl /styles/gamma.xsl   
</Directory>

Here, the alpha.xsl stylesheet is added unconditionally. Then, if the source document’s top-level element is named <root>, the beta.xsl stylesheet is added. Finally, the gamma.xsl stylesheet is unconditionally added to the processing chain. The result looks like this:

<?xml version="1.0"?>
<root>Base content . . . Alpha processed . . . Beta processed . . . Gamma processed</root>

If the root element of the source document were something other than <root>, or if that element were bound to a namespace that did not match the one used in the AxAddRootProcessor directive, only the alpha.xsl and gamma.xsl processors would be added to the processing chain. Compare this configuration snippet to the previous one:

<Directory /www/mysite.com/>
   AxAddProcessor text/xsl /styles/alpha.xsl
   AxAddRootProcessor text/xsl /styles/beta.xsl {http://myhost.tld/namspaces/root}root
   AxAddProcessor text/xsl /styles/gamma.xsl   
</Directory>

Here, the beta.xsl would not be added to the processing chain because, although the name of the top-level element matches the one in your AxAddRootProcessor directive, the directive specifies that the <root> element must be bound to the http://myhost.tld/namespaces/root namespace URI. This is not the case in our sample XML document. Therefore, only the alpha.xsl and gamma.xsl stylesheets would be applied.

Mixing various conditional processing directives at the same level can be quite powerful. Many simple sites really only use a handful of source XML grammars, and if only one result format is expected (i.e., HTML), setting up a block of carefully chosen conditional processors for the host’s top-level directory can often cover most, if not all, the processing rules for the entire site. Here is a more practical example:

# The root directory for our AxKit-enabled site
<Directory /www/mysite.com/>
   AxAddDocTypeProcessor text/xsl /styles/docbook/html/docbookx.xsl "-//OASIS//DTD DocBook
 XML V4.2//EN"
   AxAddRootProcessor text/xsl  /styles/rss2html.xsl {http://www.w3.org/1999/02/22-rdf-
syntax-ns#}RDF
   AxAddRootProcessor application/x-xsp NULL {http://apache.org/xsp/core/v1}page
   AxAddRootProcessor text/xsl /styles/appgrammar2html.xsl 
{http://apache.org/xsp/core/v1} page
</Directory>

Here, you have a small group of conditional processing directives—one that will be applied to documents containing a Document Type Definition with the SYSTEM ID indicating the latest version of DocBook, one that will be applied if the content’s top-level element is named RDF and that element is bound to the http://www.w3.org/1999/02/22-rdf-syntax-ns# namespace, and two that will match any document that contains a top-level page element bound to the http://apache.org/xsp/core/v1 namespace URI.

Just by setting up this simple block of conditional processing directives at the top level of the site, you can now publish static RSS 1.0 news feeds and DocBook documents (in their myriad forms from books to FAQs), as well as generate dynamic XML content via eXtensible Server Pages from any directory at or below this level in the site. AxKit will simply work as expected. Admittedly, this setup presumes that you will publish documents only as HTML, but that’s still a lot of power for minimal effort.

By setting two conditional AxAddRootProcessor directives to match the same root element, you create a two-step processing chain for XSP documents in which AxKit’s XSP engine processes the source, and the /styles/appgrammar2html.xsl XSLT stylesheet processes the resulting markup.

Rule 2: Conditional Processing Directives Are Evaluated Only Against the Original XML Source

The rules for style processors that are applied conditionally based on a feature contained in the XML source (AxAddDTDProcessor, AxAddRootProcessor, etc.) are only evaluated against the original source content, not subsequent transformations in the processing chain. That is, the rules are evaluated once, using the source document returned from the ContentProvider. The rules are not reevaluated. Therefore, if you have a stylesheet that changes the root element name during transformation, and that new root element coincides with the name passed to an AxAddRootProcessor directive that is in scope for that request, the AxAddRootProcessor rule does not match, since the transformed result is not examined. Similarly, only those xml-stylesheet processing instructions contained in the original source document will be considered. You cannot add to or otherwise alter the processing chain by adding a stylesheet PI to the result of a transformation.

Rule 3: Style Processors Are Prepended to the Processing Chain as You Descend into the Directory Hierarchy

Although we touched on this earlier, it bears repeating: style definitions are prepended to the processing chain as one descends into the site’s directory tree. That is, processing definitions at deeper levels in the site are added to the front of the processing chain, not to the end. This is most easily explained with a simple example using the stylesheets that illustrate AxKit processing order:

# the site's DocumentRoot directory
<Directory /www/mysite.com/>
   AxAddProcessor text/xsl /styles/alpha.xsl
</Directory>

# a child directory of the DocumentRoot
<Directory /www/mysite,com/levelone/>
   AxAddProcessor text/xsl  /styles/beta.xsl
</Directory>

# a child directory of the 'levelone' directory
<Directory /www/mysite.com/levelone/leveltwo/>
   AxAddProcessor text/xsl  /styles/gamma.xsl
</Directory>

Given this configuration, a request for http::/myhost.tld/levelone/leveltwo/minimal.xml yields the following result:

<?xml version="1.0"?>
<root>Base content . . . Gamma processed . . . Beta processed . . . Alpha processed</root>

The styles are added to the processing chain from the bottom up, so the alpha.xsl stylesheet is applied last since it is at the top level of the site’s hierarchy. Again, the reason is that, in general, most sites deploy more generic styles (those that will be applied to all or most of the documents in the site) at higher levels in the directory tree. This behavior allows that to work as expected, while handling special cases in lower-level directories by prepending special processors to the chain.

To see the utility of this behavior, imagine that you are publishing a site that is divided into several sections. In addition to the content being rendered, these sites usually have a certain amount of markup that appears on every page (a common graphical header, a legal/copyright footer, a common navigation bar, etc.). They also have a certain amount of markup that is common, but unique, to each individual section (section headers, contextual navigation, etc.). By building the processing chain from the bottom up, AxKit allows you to put the site-wide boilerplate markup in a top-level stylesheet, while handling the section-specific markup in each section’s unique directory.

# the site's DocumentRoot
<Directory /www/mysite.com/>
   # the global stylesheet that contains the site-wide headers/footers, etc.
   AxAddProcessor text/xsl  /styles/global.xsl
</Directory>

<Directory /www/mysite.com/products/>
   # adds the markup common to all pages in the 'products' section (contextual navigation,
 section headers, etc)..
   AxAddProcessor text/xsl  /styles/products.xsl
</Directory>

<Directory /www/mysite.com/locations/>
   # adds the markup common to all pages in the 'locations' section . . . 
   AxAddProcessor text/xsl  /styles/locations.xsl
</Directory>

Here, a request for a document from the products directory would be transformed by the /styles/locations.xsl stylesheet to add the section-specific content, then passed to the /styles/global.xsl stylesheet to add the site-wide boilerplate. This strategy presumes that higher-level stylesheets add only what is needed at that level and pass the rest of the markup through as-is. For example, with the above configuration, the stylesheet applied to the source content may return a result that has a div root element containing the page’s main text; that result may then be wrapped in another div by the /styles/location.xsl stylesheet to add the section-specific markup (while copying the result of the original transformation through, untouched). Finally, that result is wrapped by the /styles/global.xsl stylesheet to create the completed document. (Think: the component-based approach to constructing pages but built from the bottom up.)

Also remember that Rule 1 still applies: style definitions in the same lexical scope are evaluated in the order found in the configuration files. This means that if you have more than one processing directive that matches at a given level in the directory hierarchy, the processors for that level are still added in the order they appear, and that group of processors will be prepended to the list of processors from a higher level. An example may help clarify:

 # the site's DocumentRoot directory
<Directory /www/mysite.com/>
   AxAddProcessor text/xsl /styles/alpha.xsl
   AxAddProcessor text/xsl  /styles/beta.xsl
</Directory>

# a child directory of the DocumentRoot
<Directory /www/mysite.com/levelone/>
   AxAddProcessor text/xsl  /styles/gamma.xsl
   AxAddProcessor text/xsl  /styles/delta.xsl
</Directory>

# a child directory of the 'levelone' directory
<Directory /www/mysite.com/levelone/leveltwo/>
   AxAddProcessor text/xsl  /styles/epsilon.xsl
</Directory>

Here, using your processing order stylesheets, a request for http::/myhost.tld/levelone/leveltwo/minimal.xml returns:

<?xml version="1.0"?>
<root>Base content . . . Epsilon processed . . . Gamma processed . . . 
    Delta processed . . . Alpha processed . . . Beta processed</root>

At each level, the processors are added in the order found in the configuration file for that scope, but each of those groups of processors is added to the chain from the bottom up. Now, apply this idea to the previous sample. You can see how the page-level content can be generated for the location and products sections of the site:

# the site's DocumentRoot
<Directory /www/mysite.com/>
   # the global stylesheet that contains the site-wide headers/footers, etc.
   AxAddProcessor text/xsl  /styles/global.xsl
</Directory>

<Directory /www/mysite,com/products/>
   # generate XML from database select for 'products'
   AxAddProcessor application/x-xsp NULL
   
   # Transform 'product' application grammar to HTML
   AxAddProcessor text/xsl /styles/product2html.xsl
   
   # adds the markup common to all pages in the 'products' 
   # section (contextual navigation, section headers, etc)..
   AxAddProcessor text/xsl  /styles/products.xsl
</Directory>

<Directory /www/mysite,com/locations/>
   # generate XML from database select for 'locations'
   AxAddProcessor application/x-xsp NULL
   
   # Transform 'product' application grammar to HTML
   AxAddProcessor text/xsl /styles/locations2html.xsl

   # adds the markup common to all pages in the 'locations' section . . . 
   AxAddProcessor text/xsl  /styles/locations.xsl
</Directory>

Now, each subdirectory has two processing definitions: one that processes the XSP source content to generate markup for the location and products data from a database query, respectively, and one that transforms each domain-specific grammar into HTML. The results of those transformations are then passed to the global.xsl stylesheet, which passes that markup through, adding things like the common header, site-wide navigation, copyright information, etc. Again, in each lexical scope (the location/ and products / subdirectories), the processor definitions are added to the processing chain in the same order they are found in the configuration file. However, since they are at a lower level in the directory structure than the global stylesheet, each ordered group is processed before the global one. If you want to keep this same layout but need to allow more than a single type of document in each of the subdirectories, you could use groups of conditional processing directives instead of the unconditional AxAddProcessor directives you have here to match the properties of each supported document type. As long as the result passes up to the global stylesheet in the grammar expected, you can mix and match as needed. Everything still works.

Rule 4: Processors in the Named BlocksAre Always Evaluated Last

Essentially, named style and media blocks create their own lexical scope. When a named style or media is selected by a StyleChooser, MediaChooser, or other plug-in and there are other processing directives that match for the same request, the processors from the named blocks are always added last. Let’s break out the processing order stylesheets again and examine the details:

# the site's DocumentRoot
<Directory /www/mysite.com/>

  # First, add the QueryString StyleChooser so you can easily select named styles
  AxAddPlugin Apache::AxKit::StyleChooser::QueryString

  # Add a simple named style
  <AxStyleName styleone>
      AxAddProcessor text/xsl /styles/beta.xsl
      AxAddProcessor text/xsl /styles/gamma.xsl
  </AxStyleName>

  # Add a 'global' style
  AxAddProcessor text/xsl /styles/alpha.xsl
</Directory>

A request for the minimal.xml file in the directory that does not specify a preferred style yields the expected result—only the alpha.xsl stylesheet is applied:

<?xml version="1.0"?>
<root>Base content . . . Alpha processed</root>

But what happens if you select the named style for a request to the same document? Given Rule 1, you may expect the processors in the named style block to be applied first, since the named block appears before the global style. This is not the case. A request for minimal.xml with style=styleone in the query string gives the following:

<?xml version="1.0"?>
<root>Base content . . . Alpha processed . . . Beta processed . . . Gamma processed</root>

The styles contained in the <AxStyleName> block are added according to Rule 1. (They are at the same lexical level and are, therefore, added in the order they appear in the configuration file.) Because they are contained in that block, they are added last to the processing chain, after the unnamed, global style (alpha.xsl).

The "named processors are always last” rule also applies in cases in which a named block appears at a lower level in the document hierarchy than any matching unnamed directives. This creates a partial exception to Rule 3.

# the site's DocumentRoot
<Directory /www/mysite.com/>

  # Add the QueryString StyleChooser so you can easily select named styles
  AxAddPlugin Apache::AxKit::StyleChooser::QueryString

  # Add a 'global' style
  AxAddProcessor text/xsl /styles/alpha.xsl
</Directory>

# A first-level subdirectory
<Directory /www/mysite.com/levelone/>

  # Add a simple named style
  <AxStyleName styleone>
      AxAddProcessor text/xsl /styles/beta.xsl
      AxAddProcessor text/xsl /styles/gamma.xsl
  </AxStyleName>
</Directory>

Here, a request for http://mysite.com/levelone/minimal.xml?style=styleone gives the same result as before, despite the fact that the matching named style block is defined at a lower level in the directory tree than the global stylesheet configured for the site’s DocumentRoot:

<?xml version="1.0"?>
<root>Base content . . . Alpha processed . . . Beta processed . . . Gamma processed</root>

One final, admittedly pernicious, example reveals precisely what’s going on:

# the site's DocumentRoot
<Directory /www/mysite.com/>

  # Add the QueryString StyleChooser so you can easily select named styles
  AxAddPlugin Apache::AxKit::StyleChooser::QueryString

  # Add a 'global' style
  AxAddProcessor text/xsl /styles/alpha.xsl

# Add a simple named style
  <AxStyleName styleone>
      AxAddProcessor text/xsl /styles/beta.xsl
      AxAddProcessor text/xsl /styles/gamma.xsl
  </AxStyleName>
</Directory>

# A first-level subdirectory
<Directory /www/mysite.com/levelone/>

  # another  'global' style
  AxAddProcessor text/xsl /styles/delta.xsl
  
  # The style name here is the same as the one in the parent directory!
  <AxStyleName styleone>
      AxAddProcessor text/xsl /styles/eplison.xsl
      AxAddProcessor text/xsl /styles/zeta.xsl
  </AxStyleName>
</Directory>

Here, a request to http://mysite.com/levelone/minimal.xml?style=styleone results in the following:

<?xml version="1.0"?>
<root>Base content . . . Delta processed . . . Alpha processed . . . Epsilon processed . . . 
    Zeta processed . . . Beta processed . . . Gamma processed</root>

Surprised? Let’s take things step by step:

  1. The global alpha.xsl stylesheet is added to the processing chain.

  2. The global processor delta.xsl is configured for a directory at a lower level and is, therefore, prepended to the list of processors, according to Rule 3.

  3. The style named styleone is selected, so the two processors contained in the named block at the root level of the site (beta.xsl and gamma.xsl) are added in configuration order, according to Rule 1.

  4. The two processors (epsilon.xsl and zeta.xsl) in the named block in the levelone directory are also added in configuration order, according to Rule 1. Since they appear at a lower level in the directory tree, they are added, as a group, to the list of named styles before the two in the parent directory, according to Rule 3.

  5. All processors associated with a named style or media block are added to the processing chain after all those that are not, according to Rule 4.

The simplest way to conceptualize the processing order for rare cases such as this is that all processing directives not contained in an <AxStyleName> or <AxMediaType> blocks are combined into one list, according to Rules 1-3; a second list of those contained in a named style or media block is created, also using Rules 1-3; then the two lists are combined, with the “named styles list” always appearing last.

If all this seems a bit much, rest assured. Cases such as this last example are extremely rare in the real world. The example here is particularly nasty only because it illustrates every detail of how AxKit creates its processing chain.

Now that you have had a close look at AxKit’s many options for applying different transformative styles to XML documents. It is time to look at how those transformations happen by examining two of the more popular languages that it supports for transforming content: XSLT and XPathScript.

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.