Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return a text fragment from an XPath function?

Tags:

python

lxml

Supposing I have an XPath function I'm calling from an XSL transform using lxml (with libxml and libxslt), eg:

<xsl:template match="/">
  <xsl:variable name="result" select="myns:my-func(./*)" />
  ...
</xsl:template>

From this function, I'd like to return an XML fragment that consists the following:

some sample <em>text</em>

The python function my_func is set-up correctly using lxml to be available via the XSL stylesheet, and used lxml.html.fragments_fromstring to obtain the results which are in the format:

['some sample ', <Element em at 0x106c203b0>]

How can I return this list so it can be used later in the XSL transform from the variable as though it were collected from an XPath expression directly in the XSL context? It seems that whenever I pass a list of strings back to the XSL processor lxml throws an exception.

like image 462
Phillip B Oldham Avatar asked Oct 21 '22 05:10

Phillip B Oldham


1 Answers

The first thing is to define your function. It needs to return the node-set as a list of items. The items may include Elements (also comments and processing instructions), strings and tuples.

A hardcoded example might look like this:

from lxml import etree

def myFunc(context, parm):
  em = etree.Element('em')
  em.text = 'text'
  return ['some sample ', em]

Note that the parm parameter isn't necessary in this case, but I've included it here to match your example code which is passing a parameter in the call to myns:my-func.

And if you wanted to use the fragments_fromstring to construct your node-set, rather than building it manually, the function definition becomes even simpler.

def myFunc(context, parm):           
  import lxml.html
  return lxml.html.fragments_fromstring('some sample <em>text</em>')

Next you need to setup the namespace and register the function name.

myns = etree.FunctionNamespace('http://example.org/myNamespace')
myns['my-func'] = myFunc

And finally you can use it from within an XSLT stylesheet like this:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="http://example.org/myNamespace">
  <xsl:template match="/">                                   
    <xsl:variable name="result" select="myns:my-func(./*)" />
    <xsl:for-each select="$result">
      <xsl:copy-of select="." />
    </xsl:for-each>         
  </xsl:template>
</xsl:stylesheet>

Note the namespace URL used in the stylesheet must match the one registered in the FunctionNamespace.

Now assuming you've loaded this stylesheet into a string called xslt, an example transform might look like this:

root = etree.XML('<root></root>')
doc = etree.ElementTree(root)
transform = etree.XSLT(etree.XML(xslt))
res = transform(doc)

For a full working example, see this pastebin link.

like image 65
James Holderness Avatar answered Oct 24 '22 03:10

James Holderness