0

I am modifying a XSL that already come with some templates outputting data relative to the current context node. I want to call the same templates with a different context so I don't have to change the existing templates by passing additional parameters.

for example XML:

<anyRoot>
 <level1>
     <a>xxxxxx</a>
     <b>yyyyyy</b>
     <level2>
         <a>aaaaa</a>
         <b>bbbbbb</b>
         <c>cccccc</c>
         <d>dddddd</d>
     </level2>
 </level1>
 <level1>
     <a>zzzzzz</a>
     <b>jjjjjj</b>
     <level2>
         <a>nnnnn</a>
         <b>bbbbbb</b>
         <c>cccccc</c>
         <d>dddddd</d>
     </level2>
 </level1>
</anyRoot>

Theoretical XSL. Note that the "context=" attribute is invalid but I put it there to explain my idea:

...
<xsl:for-each select="/anyRoot/level1/level2">
   <xsl:call-template name="testTmplate"/>
   <xsl:call-template name="testTmplate" context=".."/> <!-- passing parent of level2-->
</xsl:for-each>
...

<xsl:template name="testTmplate">
    <xsl:value-of select="./a"/>
</xsl:template>

This is what I want to see as output:

aaaaa
xxxxxxx

nnnnnnn
zzzzzzz
Guasqueño
  • 405
  • 9
  • 20
  • Hi, you're doing the right thing by trying to reuse the template code! Some comments: – kjm Oct 15 '15 at 16:20
  • I'd expect the first call template to work. The second won't work but if you google the ancestor axis you should find the way the answer. Please try these changes and see how it goes. – kjm Oct 15 '15 at 16:34
  • Thanks Tim. Your answer game a very important clue, so I ended up adding a new template for the second template call (the one that pulls data from the parent). This new template acts as a wrapper template to call the existing template(s). See answer below. – Guasqueño Oct 15 '15 at 18:26

5 Answers5

3

If you want to change context, you should really be using xsl:apply-templates here, with a matching template.

For example

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />

<xsl:template match="/">
<xsl:for-each select="/anyRoot/level1/level2">
   <xsl:apply-templates select="a" />
   <xsl:apply-templates select="../a" />
</xsl:for-each>
</xsl:template>

<xsl:template match="a">
    <xsl:value-of select="."/>
    <xsl:text>&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>

However, if there was another template in your actual XSLT that also matched "a" elements, you could distinguish the one you needed by use of the mode attribute, like so:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />

<xsl:template match="/">
<xsl:for-each select="/anyRoot/level1/level2">
   <xsl:apply-templates select="a" mode="testTmplate" />
   <xsl:apply-templates select="../a" mode="testTmplate" />
</xsl:for-each>
</xsl:template>

<xsl:template match="a" mode="testTmplate">
    <xsl:value-of select="."/>
    <xsl:text>&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>

EDIT: If you really want to call an existing name template this method, simply call it from the matching template instead. Try this...

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />

<xsl:template match="/">
<xsl:for-each select="/anyRoot/level1/level2">
   <xsl:call-template name="testTmplate"/>
   <xsl:apply-templates select=".." mode="testTmplate" />
</xsl:for-each>
</xsl:template>

<xsl:template match="*" mode="testTmplate">
    <xsl:call-template name="testTmplate"/>
</xsl:template>

<xsl:template name="testTmplate">
    <xsl:value-of select="a"/>
    <xsl:text>&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>
Tim C
  • 70,053
  • 14
  • 74
  • 93
  • In the case I am trying to resolve the template (there are many more templates) is defined by name, it does not use "match" and I cannot use "select" to call a named template. Is there any solution having this constrain? – Guasqueño Oct 15 '15 at 17:18
  • I think your solution is the best work around a limitation of XSL. The guys that design XSL should seriously consider adding a new "context=" attribute to avoid all this "fixes". – Guasqueño Oct 15 '15 at 18:04
  • I've amended my answer to show how can still use your named template if you really wanted to do it that way. – Tim C Oct 15 '15 at 18:24
  • XSL named templates don't need an explicit "context" parameter because the current context is passed implicitly. See your first call to call-template in the question, why does this work? Because the current context is passed to it. So to use named templates you'll need to either change context before calling (as in the solution above) or add an explicit parameter to pass the node-set you want to process. – kjm Oct 16 '15 at 12:43
1

The ./ in your original ./a is redundant, and as @tim-c says, you would probably be better off using xsl:apply-templates. However, if you don't want to mess too much with the existing xsl:call-template and named templates setup, you can add a parameter (say, $context) that defaults to the context node and then override that as necessary:

<xsl:for-each select="/anyRoot/level1/level2">
   <xsl:call-template name="testTmplate"/>
   <xsl:call-template name="testTmplate">
       <xsl:param name="context" select=".."/> <!-- passing parent of level2-->
   </xsl:call-template>
</xsl:for-each>
...

<xsl:template name="testTmplate">
    <xsl:param name="context" select="."/>
    <xsl:value-of select="$context/a"/>
</xsl:template>

So the ./a becomes somewhat more useful as $context/a.

Tony Graham
  • 7,306
  • 13
  • 20
  • Thanks. The problem is that if I introduce a parameter for this template, I have to cascade the introduction of parameters to all other templates called from this one, and there are many in the real XSL program. – Guasqueño Oct 15 '15 at 17:34
1

A way that doesn't change your existing named templates would be to change the context using xsl:for-each:

<xsl:for-each select="/anyRoot/level1/level2">
   <xsl:call-template name="testTmplate"/>
   <xsl:for-each select=".."> <!-- parent of level2 -->
       <xsl:call-template name="testTmplate"/>
   </xsl:for-each>
</xsl:for-each>

I hesitate to recommend this since, IMO, it's not as readable, but littering many templates with $context might end up being not so readable either.

Tony Graham
  • 7,306
  • 13
  • 20
1

Thanks Tim. Your answer game a very important clue, so I ended up adding a new template for the second template call (the one that pulls data from the parent). This new template acts as a wrapper template to call the existing template(s). See answer below.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:output method="text" />

    <xsl:template match="/">
        <xsl:for-each select="/anyRoot/level1/level2">
            <xsl:call-template name="testTemplate" />
            <xsl:apply-templates select=".." mode="testTemplateWrapper" />
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="testTemplate">
        <xsl:value-of select="a"/>
        <xsl:text>&#10;</xsl:text>
    </xsl:template>

    <xsl:template match="level1" mode="testTemplateWrapper">
        <xsl:call-template name="testTemplate" />
    </xsl:template>

</xsl:stylesheet>
Guasqueño
  • 405
  • 9
  • 20
0

Wrong way below! Please see Tim's answer for the right way to do this. But, assuming you have named templates you can't or won't change and you want to reuse them please see below. Of course, depending on your current code you could add parameters to the templates or better still use unnamed templates with modes.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text" />

  <xsl:template match="/">
    <xsl:for-each select="/anyRoot/level1/level2">
      <xsl:call-template name="testTmplate"/>
    </xsl:for-each>
    <xsl:for-each select="/anyRoot/level1">
      <xsl:call-template name="testTmplate"/>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name="testTmplate">
    <xsl:value-of select="./a"/>
    <xsl:text>&#10;</xsl:text>
  </xsl:template>
</xsl:stylesheet>
kjm
  • 383
  • 3
  • 11