by Yanick Champoux
XPathScript is a XML transformation language
A Perl-based alternative to XSLT
You like XML...
But when you look at this
<xsl:template match="/">
<html>
<head>
<title>OOS Table Show</title>
</head>
<body>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="category">
<div class="category">
<xsl:apply-templates />
</div>
</xsl:template>
you see that
Created by Matt Sergeant as part of AxKit
Taken over and made into its own module by the French Connection (Dominique Quatravaux & myself)
XPathScript uses
XML::LibXML (built over libxml, excellent performance)
or
XML::XPath (pure Perl implementation, thus portable)
XPathScript is made of two distinct components
* A transformation stylesheet
(funnily enough called a template)
* A templating system
(funnily enough called the stylesheet)
* Names are probably going to be switched over in v2.0.
example-1.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<show name="OOS February 2008 Show">
<category name="Dendrobium Alliance">
<plant>
<name>Den. Big Alex x Alexandrae</name>
<owner>Ang&egrav;le Biljan</owner>
<picture photographer="Yanick Champoux">
IMG_3549.JPG</picture>
</plant>
</category>
</show>
example-1.xps
<%
$t->set( show => { showtag => 0 } );
$t->set( category => {
pre => '<table>',
post => '</table>',
intro => '<caption>{@name}</caption>'
} );
$t->set( plant => { rename => 'tr' } );
$t->set( [ qw/ name owner / ] => { rename => 'td' } );
$t->set( name => {
intro => '<b>', extro => '</b>'
} );
$t->set( picture => { action => $DO_NOT_PROCESS } );
%>
<h1>Show Results</h1>
<%~ / %>
$ xpathscript example-1.xml example-1.xps
<h1>Show Results</h1>
<table><caption>Dendrobium Alliance</caption>
<tr>
<td><b>Den. Big Alex x Alexandrae</b></td>
<td>Angèle Biljan</td>
</tr>
</table>
Anything outside of <% %> is printed verbatim
0 .----------._0 |<| verbatim | |> | `----------' | / \ / \
Include an external file (which can include external files as well, which can... well, you get the point)
<!--#include file="/path/to/stylesheet.xps" --> <h1>OOS January 2008</h1> <%~ category %>
Execute Perl code, don't output
anything
(unless print is involved)
<%
$t->set( tag => { pre => 'foo' } );
%>
<% print "\t$_\n" for @stuff; %>
<% for my $i ( @stuff ) { %>
I have some <%= $i %>
<% } %>
Execute and print out
The answer to life, the universe and everything is <%= 7 * 6 %>
Comment. Stripped out never to be seen again.
<%# what happens in comments stay in comments %>
Apply stylesheet on the nodes matching the xpath expression
<h1>OOS January Show</h1> <%# process all the plants %> <%~ /show/category/plant %>
Define a sub-template for a tag
Xpaths are relative to the processed tag
<%@ category
<h1>{@name}</h1>
<%~ plant %>
%>
<%@ plant
{name/text()}, owned by {owner/text()}
<% if ( findnodes( 'picture' ) ) { %>
pictures available
<% } %>
%>
Squish whitespaces. We can have readability and brievity!
The '-' can be unilateral as well
<h1>
<%-= get_title() -%>
</h1>
will turn into
<h1>Of Mice and Men</h1>
Query the XML DOM directly
Return the output of the node(s) processing (which can be given directly or via an xpath)
<%= map apply_templates($_),
reverse findnodes( '//plant' ) %>
Configure tags one at a time
$t->set( tag => { attributes... } )
$t->set( plant => {
pre => '<tr>',
post => '</tr>',
} );
... or a whole bunch at the same time
$t->set( [ @tags ] => { attributes...} )
$t->set( [ qw/ name owner / ] => {
rename => 'tr',
} );
Assignments are additive
$t->set( [ qw/ name owner / ] => {
rename => 'tr',
} );
$t->set( name => {
intro => '<b>',
extro => '</b>',
} );
| pre | ||
| <tag> | if showtag | |
| intro | ||
| prechildren | if has children | |
| prechild | before each child | |
| child node | ||
| postchild | after each child | |
| postchildren | if has children | |
| extro | ||
| </tag> | if showtag | |
| post |
{xpath/@values} in strings are interpolated
Interpolation can be disabled
Interpolation markup can be modified
$XML::XPathScript::current->interpolation( 1 );
$XML::XPathScript::current->interpolation_regex( qr/X<(.*?)>/ );
$t->set( category => {
pre => '<h1>X<@name></h1>',
} );
Should the tag be shown?
Default to true if no tag attribute defined, false otherwise.
# typical way to change <plant> for <tr>
$t->set( plant => {
pre => '<tr>',
post => '</tr>',
} );
Rename the tag, keep the attributes if there are any
$t->set( plant => { rename => 'tr' } )
How to process the tag and its children?
$DO_SELF_AND_KIDS (default), $DO_SELF_ONLY, $DO_NOT_PROCESS, xpath and $DO_TEXT_AS_CHILD
$t->set( plant => {
intro => '{@name/text()} - {@owner/text()}',
action => $DO_SELF_ONLY,
} );
Bring forth the big guns!
$t->set( foo => { testcode => \&do_foo } );
sub do_foo {
my ( $node, $tag ) = @_;
$tag->set({ pre => 'blah' }); # only for this node
return $action;
}
Long form of <%@ %>
Trumps all layout attributes
$t->set( category => { content => <<'END_CONTENT' } );
<h1>{@name}</h1>
<%~ plant %>
END_CONTENT
text(), comment() and '*'
$t->set( 'text()' => {
pre => '[',
post => ']',
action => $DO_TEXT_AS_CHILD,
} );
$t->set( '*' => {
testcode => sub {
warn $_[0]->name, " not in template\n"
},
} );
XPathScript does namespaces!
my $foo = $t->namespace( 'http://foo.com' );
$t->set( bar => { content => 'main namespace' } );
$foo->set( bar => { content => 'foo namespace' } );
<%perl>
use XML::XPathScript::Processor;
use XML::XPathScript::Template;
use XML::LibXML;
my $processor = XML::XPathScript::Processor->new;
$processor->set_dom(
XML::LibXML->new->parse_file( 'example-1.xml' )
);
# load the template
my $t = XML::XPathScript::Template->new;
$processor->set_template( $t );
$t->set( plant => { rename => 'tr' } );
# ...
</%perl>
<table>
<% $processor->apply_templates( '//plant' ) %>
</table>
Anything that has an XPath interface can potentially be processed by XPathScript.
use B::XPath;
use XML::XPathScript;
sub guinea_pig {
my $x = shift;
print "oink oink " x $x;
}
my $xps = XML::XPathScript->new;
$xps->set_dom( B::XPath->fetch_root( \&guinea_pig ) );
$xps->set_stylesheet( '<%~ //print %>' );
print $xps->transform;