4

I am using a recursive template that searches for some specific elements like this:

<xsl:template name="GetProdDependency">
    <xsl:param name="TechProd"></xsl:param>
    <xsl:param name="BeatenPath"></xsl:param>
    <xsl:variable name="TechProdArch" select="$TechProd/pro:own_slot_value[pro:slot_reference='technology_product_architecture']/pro:value"></xsl:variable>
    <xsl:variable name="TechProdArchNode" select="/node()/pro:simple_instance[pro:name=$TechProdArch]"></xsl:variable>
    <xsl:variable name="TechProdCompList" select="$TechProdArchNode/pro:own_slot_value[pro:slot_reference='contained_techProd_components']/pro:value"/>
    <xsl:for-each select="$TechProdCompList">
        <xsl:variable name="TechProdAsRole" select="/node()/pro:simple_instance[pro:name=current()]/pro:own_slot_value[pro:slot_reference='technology_product_as_role']/pro:value"/>
        <xsl:variable name="TechProdRole" select="/node()/pro:simple_instance[pro:name=$TechProdAsRole]/pro:own_slot_value[pro:slot_reference='role_for_technology_provider']/pro:value"/>
        <xsl:variable name="DepTechProd" select="/node()/pro:simple_instance[pro:name=$TechProdRole]"/>
        <!-- Check for beaten Path -->
        <!-- if $DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value in BeatenPath -->
        <xsl:if test="not($BeatenPath[string(.)=string($DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value)])"> 
            <!-- Do the recursion here! -->
            <!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/> (type: <xsl:value-of select="$DepTechProd/pro:type"/> and Class: <xsl:value-of select="$DepTechProd/pro:name"/>)-->
            <!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/>-->
            <xsl:value-of select="$DepTechProd"/>
            <xsl:call-template name="GetProdDependency">
                <xsl:with-param name="TechProd" select="$DepTechProd"></xsl:with-param>
                <xsl:with-param name="BeatenPath" select="$TechProd|$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
                <xsl:with-param name="Rev" select="$Rev + 1"></xsl:with-param>
            </xsl:call-template>
        </xsl:if>
    </xsl:for-each>
</xsl:template> 

This is working fine in the searching etc.

But when I get the result in the original caller, I was expecting to get a list of nodes from the calling.

I call it like:

<xsl:variable name="DelPlist">
<xsl:call-template name="GetProdDependency">
    <xsl:with-param name="TechProd" select="$TechProd"></xsl:with-param>
    <xsl:with-param name="BeatenPath" select="$TechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
    <xsl:with-param name="Rev" select="1"></xsl:with-param>
</xsl:call-template>
</xsl:variable>

And I was expecting to get a list of nodes that I can iterate through with <xsl:for-each>. But If I check for the count($DelPlist), I get 1 as result and I can not iterate.

Can somebody help?

Kangkan
  • 15,267
  • 10
  • 70
  • 113

4 Answers4

5

The answer to your question is: in XSLT 2.0 yes, in XSLT 1.0 no.

In XSLT 2.0 both templates and functions can return any value. The type of the result can be specified using the as attribute (for example as="node()*"), and you can use the xsl:sequence instruction to set the result to be the result of any XPath expression.

In XSLT 1.0, if you capture the result of xsl:call-template in a variable, the value of the variable will always be a result tree fragment.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
4

You must specify the type of the result of the template in its as attribute.

If unspecified, the type is document-node() and then in order to iterate through the result, you need to get the children of the result.

Solution: Specify the return type of the template with an as attribute.

Here is a complete example:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
     <xsl:variable name="vNodes" as="element()*">
      <xsl:call-template name="genNodes"/>
     </xsl:variable>

     <xsl:for-each select="$vNodes">
      <xsl:value-of select="concat('&#xA;', position(), ': ')"/>
      <xsl:copy-of select="."/>
     </xsl:for-each>
 </xsl:template>

 <xsl:template name="genNodes" as="element()*">
  <a/>
  <b/>
  <c/>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on any XML document (not used), the wanted, correct result is produced:

1: <a/>
2: <b/>
3: <c/>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • 1
    This is one thing that I was looking for. I shall try it out! Thanks a lot. – Kangkan Jan 03 '12 at 14:34
  • 1
    @MatthewSimoneau See;http://stackoverflow.com/questions/12568962/what-is-the-difference-between-using-xslt-xslelement-and-declaring-elements-l/12575580#comment44506877_12575580 – michael.hor257k Mar 23 '15 at 07:14
  • @michael.hor257k, I'm not going to get into an edit war over this, but yuk. Half of the text in this answer is bold. See also http://meta.stackoverflow.com/a/262086/7920 – Matthew Simoneau Mar 23 '15 at 14:50
  • Please note, this is XSLT 2.0 feature, and NOT working with XSLT 1.0 – MewX Jan 02 '19 at 23:35
  • 1
    @MewX, Yes, this *is* an XSLT2.0 solution to an XSLT 2.0 problem -- as tagged. The solution clearly starts with: ` – Dimitre Novatchev Jan 03 '19 at 01:44
2

Possible in xsl 1.0 with exslt:node-set (supported by most xslt processors):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exslt="http://exslt.org/common"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    exclude-result-prefixes="exslt msxml"
    extension-element-prefixes="exslt msxml"
    version="1.0">
    <!-- support for exslt prefix for msxml, ignored by other processors which however support exsl natively anyway -->
    <msxml:script language="JScript" implements-prefix="exslt">this['node-set']=function(x){return x}</msxml:script>
    <xsl:template match="/">
        <xsl:variable name="elementsReturnedFromCallTemplate">
            <xsl:call-template name="getElements"/>
        </xsl:variable>
        <xsl:for-each select="exslt:node-set($elementsReturnedFromCallTemplate)/*">
            <xsl:copy/>
        </xsl:for-each>
    </xsl:template>
    <xsl:template name="getElements">
        <a/>
        <b/>
    </xsl:template>
</xsl:stylesheet>

Output (tested in libxslt, xalan, saxon, msxml):

<a/>
<b/>
Ilya Kharlamov
  • 3,698
  • 1
  • 31
  • 33
2

Sorry to bother.

I could achieve what meets my requirement in the following amnner:

<xsl:template name="GetProdDependency">
    <xsl:param name="TechProd"></xsl:param>
    <xsl:param name="BeatenPath"></xsl:param>
    <xsl:variable name="TechProdArch" select="$TechProd/pro:own_slot_value[pro:slot_reference='technology_product_architecture']/pro:value"></xsl:variable>
    <xsl:variable name="TechProdArchNode" select="/node()/pro:simple_instance[pro:name=$TechProdArch]"></xsl:variable>
    <xsl:variable name="TechProdCompList" select="$TechProdArchNode/pro:own_slot_value[pro:slot_reference='contained_techProd_components']/pro:value"/>
    <xsl:for-each select="$TechProdCompList">
        <xsl:variable name="TechProdAsRole" select="/node()/pro:simple_instance[pro:name=current()]/pro:own_slot_value[pro:slot_reference='technology_product_as_role']/pro:value"/>
        <xsl:variable name="TechProdRole" select="/node()/pro:simple_instance[pro:name=$TechProdAsRole]/pro:own_slot_value[pro:slot_reference='role_for_technology_provider']/pro:value"/>
        <xsl:variable name="DepTechProd" select="/node()/pro:simple_instance[pro:name=$TechProdRole]"/>
        <!-- Check for beaten Path -->
        <!-- if $DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value in BeatenPath -->
        <xsl:if test="not($BeatenPath[string(.)=string($DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value)])"> 
            <!-- Do the recursion here! -->
            <!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/> (type: <xsl:value-of select="$DepTechProd/pro:type"/> and Class: <xsl:value-of select="$DepTechProd/pro:name"/>)-->
            <!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/>-->
            <!--<xsl:value-of select="$DepTechProd"/>-->
            <xsl:element name="TProd">
                <xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value" />
            </xsl:element>
            <xsl:call-template name="GetProdDependency">
                <xsl:with-param name="TechProd" select="$DepTechProd"></xsl:with-param>
                <xsl:with-param name="BeatenPath" select="$TechProd|$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
                <xsl:with-param name="Rev" select="$Rev + 1"></xsl:with-param>
            </xsl:call-template>
        </xsl:if>
    </xsl:for-each>
</xsl:template> 

And could iterate through the nodelist as:

<xsl:variable name="DelPlist">
<xsl:call-template name="GetProdDependency">
    <xsl:with-param name="TechProd" select="$TechProd"></xsl:with-param>
    <xsl:with-param name="BeatenPath" select="$TechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
    <xsl:with-param name="Rev" select="1"></xsl:with-param>
</xsl:call-template>
</xsl:variable>

<xsl:for-each select="$DelPlist/node()">
    <xsl:value-of select="current()" />
</xsl:for-each>

It meets my current requirement.

Kangkan
  • 15,267
  • 10
  • 70
  • 113