0

I'm wondering if there is any way how to create custom function/element which will behave similar to <xsl:for-each/>

I know there is a way how to register function/element, but none of this is capable (as far as I know) to change context and recursively execute inner XSLT instructions.

For example what I would like to achieve is this:

<myxsl:change-context name='x'>
  <xsl:value-of select='name()'/>
</myxsl:change-context>
Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
Ludek Vodicka
  • 1,610
  • 1
  • 18
  • 33
  • 1
    Why do you need a custom extension for this, can't you just use `for-each` itself? – Ian Roberts Oct 02 '14 at 13:34
  • It's because in our application (http://www.skipper18.com) is using have very complex transformations and part of XSLT functions are already rewritten by C-code. And now it would be handy to rewrite also functions which looking for data in input XML. So instead of concate-ing strings together with saxon:evaluate execute our own <...> for example. It's hard to describe in few lines, but hope it's comprehensible. – Ludek Vodicka Oct 02 '14 at 13:50

2 Answers2

1

It's possible to modify the XPath context by poking into the context object that is passed to your transform function:

void transformFunction(xsltTransformContextPtr ctxt, 
                       xmlNodePtr node, 
                       xmlNodePtr inst, 
                       xsltElemPreCompPtr comp)
{
    xmlXPathContextPtr xpctxt = ctxt->xpathCtxt;

    /* Save old context */
    xmlNodePtr oldNode = ctxt->node;
    int        oldSize = xpctxt->contextSize;
    int        oldPos  = xpctxt->proximityPosition;

    /* Set up your new context... */

    ctxt->node                = newNode;
    xpctxt->contextSize       = newSize;
    xpctxt->proximityPosition = newPos;

    /*
     * Do something under new context, probably using
     * xsltApplySequenceConstructor...
     */

    /* Restore old context */
    ctxt->node                = oldNode;
    xpctxt->contextSize       = oldSize;
    xpctxt->proximityPosition = oldPos;
}

You also might have to save and restore some other context variables. Have a look at xsltForEach in libxslt/transform.c for how libxslt implements for-each internally.

nwellnhof
  • 32,319
  • 7
  • 89
  • 113
  • Thanks for reply. I will try it as soon as possible. – Ludek Vodicka Jan 03 '15 at 19:56
  • Hi, one more thanks for your reply. Today I finally have some time to check it and try it. I posted the final solution as standalone reply. Your advice was very helpful. Only small note, storing context isn't necessary in case you will not use internal xpath query. – Ludek Vodicka Apr 03 '15 at 20:24
0

Based on @nwellnholf advice I checked transform.c and after some time I figured out how to implement it.

The core magic is inside xsltApplySequenceConstructor as @nwellnholf wrote. To be able to use it it's necessary to edit library and make this function public, because originally this function is defined in transform.c only. To do so, define this in transform.h and recompile libxslt.

XSLTPUBFUN void xsltApplySequenceConstructor(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr list, xsltTemplatePtr templ);

Second step is implement own xslt function, proceed own instructions and return processing back to xslt. These steps are done via following commands:

void elemChangeContext(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltElemPreCompPtr /*comp*/)
{
    if ( ctxt == NULL || node == NULL || inst == NULL || ctxt->insert == NULL )
        return;

    xmlNodePtr cur = /* change context to different node */;
    xmlNodePtr curInst = inst->children; //sub xslt instruction
    xsltApplySequenceConstructor(ctxt,cur, curInst,NULL);
}
Ludek Vodicka
  • 1,610
  • 1
  • 18
  • 33