Home > Back-end >  How to evaluate XSLT processor parameters in a local context?
How to evaluate XSLT processor parameters in a local context?

Time:08-26

Let's say I have this source XML:

<A>
   <B>something</B>
   <B>something else</B>
</A>

and I want to transform it into this target XML:

<C>
   <D>something</D>
   <D>something else</D>
</C>

The obvious XSL of course is this:

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="A">
      <C>
      <xsl:for-each select="B">
         <D><xsl:value-of select="."/></D>
      </xsl:for-each>
      </C>
   </xsl:template>
</xsl:stylesheet>

Now let's say I don't know the paths I'm going to use beforehand and I want to parametrize them from my processor, which happens to be lxml (in Python). So I change my XSL into this:

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:param name="path_of_B"/>
   <xsl:template match="A">
      <C>
      <xsl:for-each select="$path_of_B">
         <D><xsl:value-of select="."/></D>
      </xsl:for-each>
      </C>
   </xsl:template>
</xsl:stylesheet>

and I call it from Python like this:

source = etree.parse("source.xml")
transform = etree.XSLT(etree.parse("transform.xsl"))
target = transform(source, path_of_B="B")

This doesn't give me the intended result because when I pass the paths from the processor they are always evaluated in a global context, the current() node is always the root, no matter where I use the parameter. Is there any way to evaluate the XPaths in the correct context like they do in the first example where I write them by hand?

I have tried many approaches like

  • Passing parameters in nested templates, because I thought the evaluation would have the context of the template
  • Passing the parameters as strings and evaluate them later, but XPath 1.0 doesn't have an eval() function like Python.
  • Attribute value templates, but it is not allowed on xsl elements
  • At some point I even touched <xsl:namespace-alias> to dynamically generate my XSL but it was very confusing.

So in the end, I solved it by pre-processing my xsl file with a template engine or string-formatting. It works, but I was just wondering if there is a "pure" XSLT processor solution.

CodePudding user response:

XPath 1.0 doesn't have an eval() function

No, but the libxslt processor supports the EXSLT dyn:evaluate() extension function - so you could do:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dynamic"
extension-element-prefixes="dyn">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:param name="path_of_B"/>
   
<xsl:template match="/A">
    <C>
        <xsl:for-each select="dyn:evaluate($path_of_B)">
            <D>
                <xsl:value-of select="."/>
            </D>
        </xsl:for-each>
    </C>
</xsl:template>

</xsl:stylesheet>

CodePudding user response:

If you want to parametrize both your input and output element names you could do something like this. Although this method would not work well if your source XML's structure is not always the same.

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:param name="e1_input" select="'A'"/>
  <xsl:param name="e1_output" select="'A_OUT'"/>
  <xsl:param name="e2_input" select="'B'"/>
  <xsl:param name="e2_output" select="'B_OUT'"/>
  
  <xsl:template match="/">
    <xsl:for-each select="*[name()=$e1_input]">
      <xsl:element name="{$e1_output}">
        <xsl:for-each select="*[name()=$e2_input]">
          <xsl:element name="{$e2_output}">
            <xsl:apply-templates/>
          </xsl:element>  
        </xsl:for-each>
      </xsl:element>
    </xsl:for-each>
  </xsl:template>
   
</xsl:stylesheet>

See it working here : https://xsltfiddle.liberty-development.net/aKZkh9

  • Related