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:
pre
The scalar value assigned to this key is added to the output just before the matching element is processed.
post
The value assigned is appended to the output just after the matching element is processed.
prechildren
The scalar value assigned is sent to the output before any child elements of the matching element are visited.
postchildren
The scalar value assigned is added to the output after all children of the matching element are processed.
prechild
The value assigned is added to the output before each child of the matching element is processed.
postchild
The value assigned is added to the output after each child of the matching element is processed.
showtag
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.
testcode
The value assigned to this key is expected to reference a subroutine that controls how the matching element is processed.
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 = $1; $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 0
states that you do
not want to process the current node (nor its
children). The following is the list of possible
testcode
return values:
1
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.
-1
A return value of -1
signals the XPathScript
engine to process the current node but skip all children and their
descendants.
0
Returning 0
from any point in the
testcode
subroutine tells the XPathScript
processor to skip the current node and its descendants altogether.
$string
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.
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; }; %>
3.14.253.221