[xsl] Recursive evaluation of elements

Subject: [xsl] Recursive evaluation of elements
From: "Rowan Sylvester-Bradley" <rowan@xxxxxxxxxxxxxxxxxxxxx>
Date: Sat, 12 Dec 2009 00:08:18 -0000
I have a stylesheet that now successfully expands sub-trees using multiple
"rows" from "tables" (the rows and tables are all part of the input XML
file). I can have one expand inside another giving a two-dimensional
expansion. 

Another feature of the system that I'm building is that in the contents of
an element of the input file I can include an XPath expression surrounded by
braces. My stylesheet recognises the braces using analyze-string, and
evaluates the expression within them using saxon:eval. Parameters $p1 and
$p2 allow me to refer in these expressions to elements in the source tree
and in the current table row. 

This all works fine unless I have an expression in an inner expansion that
refers to the result of an expression in an outer expansion. In this case
what I need to happen is that the inner expression uses as its parameter the
result of the outer expression. What actually happens is the inner
expression uses the literal outer expression, without evaluation. I can see
that this is because the $source variable is set to a subtree from the
source document. But I can't see how to get it to do what I want. I tried
putting an apply-templates in several places, and immediately got a stack
overflow error, even on a small test file.

Here's a simplified version of my stylesheet:

 <xsl:stylesheet version="2.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
   xmlns:xs="http://www.w3.org/2001/XMLSchema";
   xmlns:saxon="http://saxon.sf.net/";
   exclude-result-prefixes="xs saxon">

   <xsl:strip-space elements="*"/>
   <xsl:output indent="yes"/>
   
   <xsl:key name="t" match="table" use="id"/>
   
   <xsl:template match="*" mode="#all">
      <xsl:copy>
         <xsl:apply-templates/>
      </xsl:copy>
   </xsl:template>
   
   <xsl:template match="tables" mode="#all"/>
   
   <xsl:template match="item[expand]" mode="#all">
      <xsl:variable name="here" select="."/>
      <xsl:for-each select="key('t',expand)/row">  
         <item>
            <!-- <xsl:apply-templates select="$here/(* except
item),*,$here/item" mode="expand"> -->
            <xsl:apply-templates select="$here/*" mode="expand">  
               <xsl:with-param name="source" select="$here" tunnel="yes"/>
               <xsl:with-param name="tablerow" select="." tunnel="yes"/>   
            </xsl:apply-templates>            
         </item>  
      </xsl:for-each> 
   </xsl:template>
   
   <xsl:template match="expand" mode="expand"/>
   
   <xsl:template match="text()" priority="2" mode="#all">
      <xsl:param name="source" tunnel="yes"/>
      <xsl:param name="tablerow" tunnel="yes"/>
      <xsl:analyze-string select="." regex="\{{([^\}}]+)\}}">
         <xsl:matching-substring>
            <xsl:value-of
select="saxon:eval(saxon:expression(regex-group(1)), $tablerow, $source)"/>
         </xsl:matching-substring>
         <xsl:non-matching-substring>
            <xsl:value-of select="."/>
         </xsl:non-matching-substring>   
      </xsl:analyze-string>
   </xsl:template>

</xsl:stylesheet>

And here's a sample input file:

<root>
   <items>
      <item>
         <id>a</id>
         <type>lineitem</type>
         <expand>Table1</expand>
         <name>{concat('Item A', ' result')} of expression</name>
         <description>{$p1/name}</description>
         <price>{$p1/price * 2}</price>
      </item>
      <item>
         <id>b</id>
         <type>lineitem</type>
         <expand>Table1</expand>
         <name>Item B</name>
         <price>{$p1/price * 2}</price>
         <item>
            <id>b1</id>
            <type>delivery</type>
            <expand>Table2</expand>
            <name>{concat($p2/../name, ' delivered by ', $p1/name)}</name>
            <price>{$p1/price * 3}</price>
         </item>
      </item>
      <item>
         <id>c</id>
         <type>lineitem</type>
         <expand>Table1</expand>
         <name>Item C ({$p1/name})</name>
         <price>{$p1/price * 2}</price>
         <item>
            <id>c1</id>
            <type>delivery</type>
            <expand>Table2</expand>
            <name>{concat($p2/../name, ' delivered by ', $p1/name)}</name>
            <!-- <price>{$p2/../price + $p1/price}</price> -->
         </item>
      </item>
   </items>
   <tables>
      <table>
         <id>Table1</id>
         <row>
            <name>A nasty cheap one</name>
            <price>0.49</price>
         </row>
         <row>
            <name>This one's gold plated</name>
            <price>9.99</price>
         </row>
      </table>
      <table>
         <id>Table2</id>
         <row>
            <name>Second Class</name>
            <price>0.25</price>
         </row>
         <row>
            <name>First Class</name>
            <price>0.50</price>
         </row>
         <row>
            <name>Express</name>
            <price>1.00</price>
         </row>
      </table>
   </tables>
</root>
      
Items 'a' and 'b' expand fine, and the braces expressions evaluate
correctly.

Item 'c' expands OK, but the value of the name element in item c1 is:
<name>Item C ({$p1/name}) delivered by Second Class</name>

What I need is:
<name>Item C (A nasty cheap one) delivered by Second Class</name>

Can anyone suggest how to do this?

Many thanks - Rowan

Current Thread