Processing XML Stylesheet Transformations (Perl Cookbook, 2nd Edition)

## 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

Copyright © 2003 O'Reilly & Associates. All rights reserved.