PHP XML
Calling PHP Functions from XSLT Stylesheets
Problem
You want to call PHP functions from within an XSLT stylesheet.
Solution
Invoke the XSLTProcessor::registerPHPFunctions() method to enable this functionality:
$xslt = new XSLTProcessor();
$xslt->registerPHPFunctions();
And use the function() or functionString() function within your stylesheet:
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl"
xsl:extension-element-prefixes="php">
<xsl:template match="/">
<xsl:value-of select="php:function('strftime', '%c')" />
</xsl:template>
</xsl:stylesheet>
Discussion
XSLT parameters are great when you need to communicate from PHP to XSLT. However, they’re not very useful when you require the reverse. You can’t use parameters to extract information from the stylesheet during the transformation. Ideally, you could call PHP functions from a stylesheet and pass information back to PHP.
Fortunately, there’s a method that implements this functionality: registerPHPFunctions(). Here’s how it’s enabled:
$xslt = new XSLTProcessor();
$xslt->registerPHPFunctions();
This allows you to call any PHP function from your stylesheets. It’s not available by default because it presents a security risk if you’re processing stylesheets controlled by other people.
Both built-in and user-defined functions work. Inside your stylesheet, you must define a namespace and call the function() or functionString() methods, as shown:
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl"
xsl:extension-element-prefixes="php">
<xsl:template match="/">
<xsl:value-of select="php:function('strftime', '%c')" />
</xsl:template>
</xsl:stylesheet>
At the top of the stylesheet, define the namespace for PHP: http://php.net/xsl. This example sets the namespace prefix to php. Also, set the extension-element prefixes value to php so XSLT knows these are functions.
To call a PHP function, reference php:function(). The first parameter is the function name; additional parameters are the function arguments. In this case, the function name is strftime and the one argument is %c. This causes strftime to return the current date and time.
This example uses the stylesheet, stored as strftime.xsl, to process a single-element XML document:
$dom = new DOMDocument;
$dom->loadXML('<blank/>');
$xsl = new DOMDocument;
$xsl->load(__DIR__ . '/strftime.xsl');
$xslt = new XSLTProcessor();
$xslt->importStylesheet($xsl);
$xslt->registerPHPFunctions();
print $xslt->transformToXML($dom);
Mon Jul 22 06:01:10 2014
This works like standard XSLT processing, but there’s an additional call to registerPHP Functions() to activate PHP function support.
You can also return DOM objects. Takes the XML address book and mangles all the email addresses to turn the hostname portion into three dots. Everything else in the document is left untouched.
Example Spam protecting email addresses
function mangle_email($nodes) {
return preg_replace('/([^@\s]+)@([-a-z0-9]+\.)+[a-z]{2,}/is',
'$1@...',
$nodes[0]->nodeValue);
}
$dom = new DOMDocument;
$dom->load(__DIR__ . '/address-book.xml');
$xsl = new DOMDocument;
$xsl->load(__DIR__ . '/mangle-email.xsl');
$xslt = new XSLTProcessor();
$xslt->importStylesheet($xsl);
$xslt->registerPhpFunctions();
print $xslt->transformToXML($dom);
Inside your stylesheet, create a special template for /address-book/person/email elements. As an example:
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl"
xsl:extension-element-prefixes="php">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/address-book/person/email">
<xsl:copy>
<xsl:value-of select="php:function('mangle_email', node())" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The first template ensures that the elements aren’t modified, and the second passes the current node to PHP for mangling. In the second template, the mangle_email() function is passed the current node, represented in XPath as node(), instead of a string. Be sure not to place the node() inside quotation marks, or you’ll pass the literal text node().
Nodes become DOM objects inside PHP and always arrive in an array. In this case, mangle_email() knows there’s always only one object and it’s a DOMText object, so the email address is located in $nodes[0]->nodeValue.
When you know that you’re only interested in the text portion of a node, use the functionString() function. This function converts nodes to PHP strings, which allows you to omit the array access and nodeValue dereference:
function mangle_email($email) {
return preg_replace('/([^@\s]+)@([-a-z0-9]+\.)+[a-z]{2,}/is',
'$1@...',
$email);
}
// all other code is the same as before
The new stylesheet template for /address-book/person/email is:
<xsl:template match="/address-book/person/email">
<xsl:copy>
<xsl:value-of
select="php:functionString('mangle_email', node())" />
</xsl:copy>
</xsl:template>
The mangle_email() function now processes $email instead of $nodes[0]->nodeValue because the template now calls the functionString() function.
The function() and functionString() methods are incredibly useful, but using them undermines the premise of XSL as a language-neutral transformation engine. When you call PHP from XSLT, you cannot easily reuse your stylesheets in projects that use Java, Perl, and other languages, because they cannot call PHP. Therefore, you should consider the trade-off between convenience and portability before using this feature.
No comments:
Post a Comment