AxKit.org [logo curtesy of http://xml.com]
--sep--
Start Navigation
About AxKit
Index
xml.apache.org
Features
Live Sites
Installation
Documentation
Daily Churn
Getting AxKit
License
Download
Mailing List
Contribute
CVS
Support
Bugs
End Navigation
Prev Top Next

The Template Hash

The apply_templates() function iterates over the nodes of your XML file applying the templates in the $t hash reference. This is the most important feature of XPathScript, because it allows you to define the appearance for individual tags without having to do it programmatically. This is the declarative part of XPathScript. There is an important point to make here: XSLT is a purely declarative syntax, and people are having to work procedural code into XSLT via work arounds. XPathScript takes a much more pragmatic approach (much like Perl itself) - it is both declarative and procedural, allowing you the flexibility to use real code for real problems. It is important to note that apply_templates returns a string, so you must either use print apply_templates() if using it from a Perl section of code, or via <%= apply_templates() %>.

The keys of $t are the names of the elements, including namespace prefixes. When you call apply_templates(), every element visited is looked up in the $t hash, and the template items stored in that hash are applied to the node. It's worth noting at this point, that unlike XSLT, XPathScript does not perform tree transformations from one tree to another. It simply sends its output to the browser directly. This has advantages and disadvantages, but they are beyond the scope of this guide.

The following sub-keys define the transformation:

  • pre - the output to occur before the tag.
  • post - the output to occur after the tag.
  • prechildren - the output to occur before the children of this tag are output.
  • postchildren - the output to occur after the children of this tag are output.
  • prechild - the output to occur before each child of this tag.
  • postchild - the output to occur after each child of this tag.
  • showtag - set to a true value to display the tag as well as the pre and post values. If unset or false the tag itself is not displayed.
  • testcode - code to execute upon visiting this tag. See below.
The showtag option is mostly equivalent to the XSLT <xsl:copy> tag, only less verbose. The pre and post options are useful because generally in transformations we want to specify what comes before and after a tag. For example, to change an HTML A tag to be in italics, but still have the link, we would use the following:
$t->{A}{pre} = "<i>";
$t->{A}{post} = "</i>";
$t->{A}{showtag} = 1;

"testcode"

The testcode option is where we perform really powerful transformations. Its how we can do more complex tests on the node that are available in XPath, and locally modify the transformation based on what we find.

The value stored in testcode is simply a reference to a subroutine. In Perl these are incredibly simple to create using the anonymous sub keyword (note that these are often erroneously called closures, but they only become closures if they reference a lexical variable outside the scope of the subroutine itself). The sub is called every time one of these elements is visited. The subroutine is passed two parameters: The node itself, and an empty hash reference that you can populate using the pre, post, prechildren, prechild, postchildren, postchild and showtag values that we've discussed already. Unlike the global $t hashref you don't have to first specify the element name as a key. Here's the <ulink> example from the global tags code above:

$t->{'ulink'}{testcode} = sub { 
	my ($node, $t) = @_;
	$t->{pre} = '<i><a href="' . findvalue('@url', $node) . '">';
	$t->{post} = '</a></i>';
	return 1;
};
The equivalent XSLT code looks like this:
<xsl:template match="ulink">
	<i><a>
		<xsl:attribute name="href">
			<xsl:value-of select="@url"/>
		</xsl:attribute>
		<xsl:apply-templates/>
	</a></i>
</xsl:template>
Note in the XPathScript above that the inner $t is lexically scoped, so changes to it don't affect the outer $t. To save some confusion we might have named that variable $localtransforms, but some people like myself hate typing... ;-)

The return value from the testcode is also important. A return value of 1 means to process this node and continue processing all the children of this node. A return value of -1 means to process this node and stop, and a return value of 0 means do not process this node at all. This is useful in conditional tests, where you may not wish to process the nodes under certain conditions. You may also use a return code of a consisting of a string that is an XPath expression. See An XPathScript Mini-Reference for more information.

It is important to note that we can do things here based on XPath lookups just as we can in XSLT. While it is a little more verbose than a simple XSLT pattern match, the trade off is in performance. An example is in XSLT you might match artheader/title and elsewhere you might match title[name(..) != "artheader". In XPathScript we can only match "title" in the template hash. But we can use the testcode section to extend the match:

$t->{'title'}{testcode} = sub { 
	my $node = shift;
	my $t = shift;
	if (findvalue('parent::blockquote', $node)) {
		$t->{pre} = "<b>";
		$t->{post} = "</b><br>\n";
	}
	elsif (findvalue('parent::artheader', $node)) {
		$t->{pre} = "<h1>";
		$t->{post} = "</h1>";
	}
	else {
		my $parent = findvalue('name(..)', $node);
		if (my ($level) = $parent =~ m/sect(\d+)$/) {
			$t->{pre} = "<h$level>";
			$t->{post} = "</h$level>";
		}
	}

	return 1;
};
Here we check what the parent node is before performing our modification to the local $t hashref. Specifically note the utility of being able to perform Perl regular expressions to extract values.

Copying styles

One really neat feature of XPathScript that is really hard to do with XSLT is to be able to copy a style completely:

<%
$t->{'foo'}{pre} = "<i>";
$t->{'foo'}{post} = "</i>";
$t->{'foo'}{showtag} = 1;

$t->{'bar'} = $t->{'foo'};
%>
While this would be possible in XSLT using entities, it's certainly not very practical or neat. With XPathScript many tags can share the same template. Be careful though - this is a reference copy, not a deep copy, so the following may not do what you think it should:
<%
$t->{'foo'}{pre} = "<i>";
$t->{'foo'}{post} = "</i>";
$t->{'foo'}{showtag} = 1;

$t->{'bar'} = $t->{'foo'};
$t->{'bar'}{post} = "</i><br>";
%>
Because this is a reference, the last line there changes the values for 'foo' as well as 'bar'.

A "Catch All"?

Does XPathScript have a "catch all" option for elements that I don't have a $t entry for? Yes, of course! Simply set $t->{'*'} to the template you want to execute. You can even do some really clever things, such as using the testcode section to output a warning to the Apache error log about an unrecognised tag, rather than having to place some output in the resulting document and bother your users!

This feature was introduced in AxKit 0.94.

Interpolation

Adding attributes or other data into the translated nodes is non-trivial using this setup. It requires you to drop down into testcode. Here's an example of turning <link url="..."> tags into HTML <a> tags:

<%
$t->{'link'}{testcode} = sub {
  my ($node, $t) = @_;
  $t->{pre} = '<a href="' . $node->findvalue('@url') . '">';
  $t->{post} = '</a>';
  return 1;
};
%>
This is obviously rather verbose.

To make this a little simpler, in XPathScript as of AxKit 1.1, we have introduced interpolation of the replacement strings, much the same as you can do with XSLT attributes. Here is the appropriate $t entry as of AxKit 1.1:

<%
$t->{'link'}{pre} = '<a href="{@url}">';
$t->{'link'}{post} = '</a>';
%>
The curly brackets {} delimit an XPath expression on which findvalue is called using the current node as the context. Any XPath expression should be valid within those delimiters.

As a backwards compatibility measure, and to ensure efficiency is defaulted, interpolation only occurs when you have the following somewhere in your Apache configuration defined for the current request:

PerlSetVar AxXPSInterpolate 1
You can also turn off interpolation temporarily in your script using the global variable $XPathScript::DoNotInterpolate. Set that to a true value to turn off interpolation. Be careful to only do that locally (using the perl local keyword) to ensure it doesn't remain set for the next invocation of the script.


Prev Top Next

Printer Friendly
Raw XML