12

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.

16
  • 1
    I did a longer research on this, and I came up with the same solution as stated in the last sentence: You'd need to return another DOMDocument from this function. But then it gets bugly again, because I just got out the plain text and no nodes. (xsl:for-each didn't help either) Commented Oct 18, 2012 at 22:11
  • @DanLee: Thanks for the feedback. Just tried with an iterator but it's no joy, too: "Warning: A PHP Object cannot be converted to a XPath-string" - then I've taken a look into source and it only handles object that are an instance of some DomNode - so getting all node's xpaths, union them and returning the "real" DomNodeList didn't work either. It's a mess :) It's probably worth to suggest allowing an array of dom nodes here as a return value. Commented Oct 18, 2012 at 22:44
  • I linked the wrong library, but the code is identical: lxr.sweon.net/php/http/source/ext/xsl/xsltprocessor.c#L331 Commented Oct 18, 2012 at 23:10
  • 1
    Yes indeed it's a mess and I think your suggestion should be added. Maybe you should file a bug report. By the way I was working with the help of this php.net comment: php.net/manual/en/xsltprocessor.registerphpfunctions.php#59280 Commented Oct 19, 2012 at 14:10
  • +1 to bring out this. Can you provide more details about your function all_but_first with example array, your sample xml, xsl? I am interested in this question and have executed a basic example from php.net. I would like to go in depth with this concept because might be I will have this type of requirement in future. Your sample (xml, xsl and value of array in function) data would be helpful to me to go further on this topic. Commented May 15, 2013 at 6:45

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

I wasn't aware of your answer until right now. Thanks a lot, this really works :) - Great!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.