[ Team LiB ] Previous Section Next Section

Recipe 22.7 Processing XML Stylesheet Transformations

22.7.1 Problem

You have an XML stylesheet that you want to use to convert XML into something else. For example, you want to produce HTML from files of XML using the stylesheet.

22.7.2 Solution

Use XML::LibXSLT:

use XML::LibXSLT;

my $xslt       = XML::LibXSLT->new;

my $stylesheet = $xslt->parse_stylesheet_file($XSL_FILENAME);
my $results    = $stylesheet->transform_file($XML_FILENAME);

print $stylesheet->output_string($results);

22.7.3 Discussion

XML::LibXSLT is built on the fast and powerful libxslt library from the GNOME project. To perform a transformation, first build a stylesheet object from the XSL source and then use it to transform an XML file. If you wanted to (for example, your XSL is dynamically generated rather than being stored in a file), you could break this down into separate steps:

use XML::LibXSLT;
use XML::LibXML;

my $xml_parser  = XML::LibXML->new;
my $xslt_parser = XML::LibXSLT->new;

my $xml         = $xml_parser->parse_file($XML_FILENAME);
my $xsl         = $xml_parser->parse_file($XSL_FILENAME);

my $stylesheet  = $xslt_parser->parse_stylesheet($xsl);
my $results     = $stylesheet->transform($xml);
my $output      = $stylesheet->output_string($results);

To save the output to a file, use output_file:

$stylesheet->output_file($OUTPUT_FILENAME);

Similarly, write the output to an already-opened filehandle with output_fh:

$stylesheet->output_fh($FILEHANDLE);

It's possible to pass parameters to the transformation engine. For example, your transformation might use parameters to set a footer at the bottom of each page:

$stylesheet->transform($xml, footer => "'I Made This!'");

The strange quoting is because the XSLT engine expects to see quoted values. In the preceding example, the double quotes tell Perl it's a string, whereas the single quotes are for the XSLT engine.

You can even retrieve data unavailable to XSLT (from a database, etc.) or manipulate runtime XSLT data with Perl. Consider the file in Example 22-10, which has multilingual title tags.

Example 22-10. test.xml
   <list>
     <title>System</title>
     <TituloGrande>Products</TituloGrande>
     <sublist>
       <SubTitleOne>Book</SubTitleOne>
     </sublist>
   </list>

You'd like to call a Perl function match_names from the XSLT template, test.xsl, given in Example 22-11.

Example 22-11. test.xsl
   <?xml version="1.0" encoding="UTF-8"?>
   <xsl:stylesheet
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
     xmlns:test="urn:test">
   <xsl:template match="/">

     <xsl:variable name="matched" select="test:match_names('title | 
titulo | titre | titolo', . )" />

     <xsl:for-each select="$matched">
       <xsl:copy-of select="." />
     </xsl:for-each>

   </xsl:template>
   </xsl:stylesheet>

The arguments to match_names are a regex and a node list, and the function is expected to return a node list object. Use XML::LibXML::NodeList methods to work with the parameters and create the return value. The match_names subroutine is given in Example 22-12.

Example 22-12. match_names subroutine
   sub match_names {
     my $pattern = shift;
     my $nodelist = shift;
     my $matches = XML::LibXML::NodeList->new;
     foreach my $context ($nodelist->get_nodelist) {
       foreach my $node ($context->findnodes('//*')) {
         if ($node->nodeName =~ /$pattern/ix) {
           $matches->push($node);
         }
       }
     }
     return $matches;
   }

Use XML::LibXSLT's register_function method to register your function for use from an XSLT template. Here's how you'd process the template from Example 22-10:

use strict;
use XML::LibXML;
use XML::LibXSLT;

my $xml_parser = XML::LibXML->new;
my $xslt_parser = XML::LibXSLT->new;

sub match_names { ... }   # as in example 22-10
$xslt_parser->register_function("urn:test", "match_names", \&match_names);
my $dom = $xml_parser->parse_file('test.xml');
my $xslt_dom = $xml_parser->parse_file('test.xsl');
my $xslt = $xslt_parser->parse_stylesheet($xslt_dom);
my $result_dom = $xslt->transform($dom);
print $result_dom->toString;

Use closures to let XSLT access to Perl variables, illustrated here with an Apache request object:

   $xslt->register_function("urn:test", "get_request",
                            sub { &get_request($apache_req,@_) } );

The get_request XSLT function (named in the second argument to register_function calls the Perl subroutine get_request (named in the code that is the third argument) with $apache_req preceding any arguments given to the XSLT function. You might use this to return a node list containing HTTP form parameters, or to wrap DBI database queries.

22.7.4 See Also

The documentation for the modules XML::LibXSLT and XML::LibXML

    [ Team LiB ] Previous Section Next Section