Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter a select nodeset with a PHP function?

Tags:

php

xslt

I wonder if and how it is possible to register a PHP userspace function with the XSLT processor that is able not only to take an array of nodes but also to return it?

Right now PHP complains about an array to string conversion using the common setup:

function all_but_first(array $nodes) {        
    array_shift($nodes);
    shuffle($nodes);
    return $nodes;
};

$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStylesheet($xslDoc);
$buffer = $proc->transformToXML($xmlDoc);

The XMLDocument ($xmlDoc) to transform can for example be:

<p>
   <name>Name-1</name>
   <name>Name-2</name>
   <name>Name-3</name>
   <name>Name-4</name>
</p>

Within the stylesheet it's called like this:

<xsl:template name="listing">
    <xsl:apply-templates select="php:function('all_but_first', /p/name)">
    </xsl:apply-templates>
</xsl:template>

The notice is the following:

Notice: Array to string conversion

I don't understand why if the function gets an array as input is not able to return an array as well?

I was also trying other "function" names as I've seen there is php:functionString but all tried so far (php:functionArray, php:functionSet and php:functionList) did not work.

In the PHP manual it's written I can return another DOMDocument containing elements, however then those elements aren't from the original document any longer. That does not make much sense to me.

like image 743
hakre Avatar asked Oct 18 '12 17:10

hakre


1 Answers

Something that works for me is to return an instance of DOMDocumentFragment instead of an array. So to try it on your example, I saved your input as foo.xml. Then I made foo.xslt look like this:

<xsl:stylesheet version="1.0" xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
        xmlns:php="http://php.net/xsl">
    <xsl:template match="/">
        <xsl:call-template name="listing" />
    </xsl:template>
    <xsl:template match="name">
        <bar> <xsl:value-of select="text()" /> </bar>
    </xsl:template>
    <xsl:template name="listing">
        <foo>
            <xsl:for-each select="php:function('all_but_first', /p/name)">
                <xsl:apply-templates />
            </xsl:for-each>
        </foo>
    </xsl:template>
</xsl:stylesheet>

(This is mostly just your example with a xsl:stylesheet wrapper to invoke it.) And the real heart of the matter, foo.php:

<?php

function all_but_first($nodes) {
    if (($nodes == null) || (count($nodes) == 0)) {
        return ''; // Not sure what the right "nothing" return value is
    }
    $returnValue = $nodes[0]->ownerDocument->createDocumentFragment();
    array_shift($nodes);
    shuffle($nodes);
    foreach ($nodes as $node) {
        $returnValue->appendChild($node);
    }
    return $returnValue;
};

$xslDoc = new SimpleXMLElement('./foo.xslt', 0, true);
$xmlDoc = new SimpleXMLElement('./foo.xml', 0, true);

$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStylesheet($xslDoc);
$buffer = $proc->transformToXML($xmlDoc);
echo $buffer;

?>

The important part being the call to ownerDocument->createDocumentFragment() to make the object that gets returned from the function.

like image 126
csd Avatar answered Nov 19 '22 16:11

csd