6.2 The Template Hash: A Closer Look


Understanding how the template hash works is crucial to the effective use of XPathScript. First, the template hash is a bit magical in that (unlike all other Perl variables you may use in your stylesheet) you need not initialize this hash in your stylesheet. It is declared invisibly during execution by the XPathScript processor. This means that you cannot safely initialize a scalar named $t to the top level of your XPathScript stylesheets without causing a conflict. Second, the template hash really takes the form of a hash of hashes. The names given to the top-level keys define the element names that will be matched when apply_templates( ) is called, and the values for those keys are themselves hashes whose predefined keys determine how the given element will be processed if a match is found. A typical rule takes the following form:

   $t->{<element name>}{<sub-key name>} = $some_value;   

element name is the full name (including the namespace prefix) of the element you want to match, and sub-key name is one of eight special keys that the XPathScript processor uses to determine how to build the output for matching cases.

Here are all these special subkeys and their associated behaviors:


The scalar value assigned to this key is added to the output just before the matching element is processed.


The value assigned is appended to the output just after the matching element is processed.


The scalar value assigned is sent to the output before any child elements of the matching element are visited.


The scalar value assigned is added to the output after all children of the matching element are processed.


The value assigned is added to the output before each child of the matching element is processed.


The value assigned is added to the output after each child of the matching element is processed.


If defined, this key has the effect of copying the current matching element's start and end tags into the output. By default, the start/end tags for all matching elements are stripped.


The value assigned to this key is expected to reference a subroutine that controls how the matching element is processed.

6.2.1 The Joys of testcode

The testcode key is far and away the most powerful and interesting of the template rule subkeys. The value assigned to this key references a Perl subroutine that handles the output. Each time a match is found for the top-level key, this subroutine is called and passes two arguments: the element node for the current match and a localized template hash reference that can be used to set the other subkeys for the current template. The private template hash can be used in the same way as the global template hash, but its effects are limited to the current node, and there is no need to add the top-level key containing the element name.

 # Set the pre and post keys for an <item> elements template in the typical way $t->{item}{pre} = '<li>'; $t->{item}{pre} = '</li>'; # The same, but via testcode $t->{item}{testcode} = sub {     my ($node, $template ) = @_;     $template->{pre}  =  '<li>';     $template->{post} = '</li>'; }; 

Perl code executed inside the mod_perl environment needs to be a bit stricter than that allowed in typical CGI scripts. Specifically, you should avoid creating closures (subroutines that reference lexical variables outside the scope of the subroutine itself) when creating your testcode subroutines, or you will certainly get unexpected results from your XPathScript stylesheets. For more detailed information about closures and mod_perl , see the reference pages at http://perl.apache.org/docs/general/perl_reference/perl_reference.html#.

The testcode option also provides a way to conditionally process a given element based on the properties of the element node itself. In XSLT, this kind of property examination often happens when the XPath expression for a given template rule is evaluated, but in XPathScript, template matches are made only against the element name, and you need to examine the node properties (or other conditions) from within the template's testcode subroutine.

Suppose that you are transforming DocBook XML documents into HTML. Even if you are using the simplified subset ( sdocbook.dtd ), the title element can validly appear in 22 different contexts; thus, a single, simple template rule based on the element name cannot suffice. The following shows how you could begin to approach the problem:

 $t->{title}{testcode} = sub {     my ($node, $template ) = @_;                  # Get the parent element's name     my $parent_name = $node->findvalue('name(..)');          # now alter the local template hash to cover the various cases.     if ( $parent_name eq 'section' ) {         my $level = $node->findvalue('count(ancestor::section)');         $template->{pre}  = "<h$level>";         $template->{post} = "</h$level>";         return 1;     }     elsif ( $parent_name =~ m/sect(\d+)$/ ) {         my $level = ;         $template->{pre}  = "<h$level>";         $ttemplate->{post} = "</h$level>";          return 1;     }        else {         return 0;     } }; 

Here, you grab the name of the current title element's parent element and examine that value to provide the context for your conditional processing. If the parent is a section element, you count the number of nested sections and use that to determine the level for the HTML header element that will be used to render the content. If the parent name matches the regular expression /sect(\d+)$/ , the level of the header is defined by extracting the numeric value from the element name itself (i.e., sect1 , sect2 , etc.). Obviously, this does not cover the 22 possible cases in which a title element can appear, but it provides a good start that you can build on later, as needed.

You return different values from your title element's testcode subroutine, depending on the context. This is an important option, since the value returned from the testcode subroutine controls how the XPathScript processor reacts when a matching node is found. A return value of 1 indicates to the XPathScript processor that you want to process the current node and its children, while returning states that you do not want to process the current node (nor its children). The following is the list of possible testcode return values:


The default behavior for all templates; returning 1 from the testcode subroutine tells the XPathScript processor to process the current node and descend into any child nodes for additional template matches.


A return value of -1 signals the XPathScript engine to process the current node but skip all children and their descendants.

Returning from any point in the testcode subroutine tells the XPathScript processor to skip the current node and its descendants altogether.


If a string is returned from the testcode subroutine, it is expected to contain a valid XPath expression that will be used to select the next set of nodes to process.

Given the access that the testcode option provides to the node being selected and the variety of return codes that control what the XPathScript processor does after the associated subroutine has been executed, you can safely say that XPathScript's element-name-only template matching rule behavior is in no way inferior to XSLT's template matching rules (in which more sophisticated XPath expressions may be used to decide when a template is applied). The only difference is that, in XPathScript, any additional node properties are examined via the given testcode subroutine, not tested by the match rule itself.

6.2.2 The Special "Catch-All" Template

There is one exception to the rule that template matches can only be made against element names: the special "catch-all" template that is invoked by using a top-level key in the template hash with the name « * ». The subkeys added to the catch-all template are invoked for every element node that does not already have an explicit entry in the template hash. Among other uses, this allows you to explicitly prune all elements from the current node that do not have an associated template rule.

 <% # Main template rules here . . .  # But block all unexpected elements $t->{'*'}->{testcode} = sub { return 0; }; %> 

If you want to be a little fancier, you can log the rogue elements in the Apache error.log for debugging during development:

 <% # Block and log all unexpected elements $->{'*'}->{testcode} = sub {     my ($node, $template) = @_;     AxKit::Debug(10, "Unexpected element '" . $node->getName . "'  found during XPathScript transformation.");     return 0;  }; %> 

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