XSL named parameter 'with-param' using 'apply-templates'
Asked Answered
N

3

22

My questions are at the bottom of this post, if you wish to read them before the full explanation.

I'm converting an XML document to a pretty web page using XSL, and am having trouble with correctly passing a variable. I have many xsl:templates defined, and need to pass a specific parameter to just one of them. I was hoping that I would be able to pass a named parameter that would presumably be sent to all of the xsl:templates, but only be used by a single one and ignored by the others. However, when trying to test this for myself (and my limited understanding of XSL), I was unable to pass the parameter at all, let alone test if it was accidentally disturbing any other xsl:templates.

The following is simplified example code (typed up for this question, it may contain a typo or two). I have many many different xsl:templates defined to deal with nodes in the XML, and everything has been working fine until now. It is in adding a parameter to these templates that I appear to be having issues.

XML file:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="main.xsl"?>
<wrapperNode>
  <testNode>
    <subNode/>
  </testNode>
</wrapperNode>

main.xsl:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="test.xsl"/>
<xsl:output method="html" indent="yes"/>

<xsl:template match="/">

<html>
  <body>
      <xsl:apply-templates>
        <xsl:with-param name="testParam">TEST_PARAMETER</xsl:with-param>
      </xsl:apply-templates>
  </body>
</html>

</xsl:template>
</xsl:stylesheet>

test.xsl:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="testNode">
  <xsl:param name="testParam" />
  TEST1
  <xsl:value-of select="$testParam" />
  TEST2
</xsl:template>
</xsl:stylesheet>

Output (actual):

TEST1 TEST2

Output (expected/desired):

TEST1 TEST_PARAMETER TEST2

My questions in regards to this:

  1. Is it possible to send a named parameter to all of my xsl:templates using an xsl:apply-templates with xsl:with-param, but select this value specifically by name= within the actual template so that it can be explicitly used in a single template and ignored by all others (even if I wanted to add other, differently named, parameters for other templates later)?

  2. What am I doing wrong with my current sample code that it does not seem to receive the parameter at all?

  3. Is there a better way to accomplish this?

Edit: I want to make it clear that due to other output within the test.xsl:testNode template, I know for sure that it IS being successfully called. It is ONLY the parameter part that is not working. I do not mean to waste people's time figuring out why that template is not being called. It is.

Update: In response to the answers I initially received, which pointed out that the example I made up was not completely correct (my mistake) and did not very clearly show the issue (ie: that the correct template is being called, but that only the parameter appears to not be working), I have replaced the examples with much better ones. This example more clearly shows that the testNode template is successfully being called, but that the parameter does not seem to be passed. I have tested this numerous times, before and after consideration of the previous answers to this question. I am absolutely stumped, as everything appears to be correct from what I have read elsewhere and what people have suggested so far.

Northeastwards answered 18/11, 2010 at 21:13 Comment(3)
As @Jim Garrison have answer you can't have a null namespace URI element as a top element of the stylesheet. Your XSLT processor should throw an error message.Tommi
Good question, +1. See my reply for concrete answers to your questions.Characteristic
I have updated this question with a more accurate and specific example. My apologies for having a less-than-perfect example in the first place, I was creating it on the fly and misplaced a few things (such as the HTML tags as Jim pointed out).Northeastwards
C
37

My questions in regards to this:

  1. Is it possible to send a named parameter to all of my xsl:templates using an xsl:apply-templates with xsl:with-param, but select this value specifically by name= within the actual template so that it can be explicitly used in a single template and ignored by all others (even if I wanted to add other, differently named, parameters for other templates later)?

Yes. In XSLT 2.0 one may use the so called "tunnel parameters", but in XSLT 1.0 this is the way to have some parameters reach some remote template down the chain.

Another way is to have global parameters, so that they wouldn't have to be passed through every template in the chain.

.2. What am I doing wrong with my current sample code that it does not seem to receive the parameter at all?

The reason is in the code you haven't shown to us. Or maybe the source XML document you have in your real case isn't the same as the one provided here. I ran the provided code and I couldn't repro the problem at all -- the desired output is produced.

My guess is that some other template is selected before the template that matches testNode. This template doesn't know anything about the passed parameter and it doesn't pass it to the templates that it, on its turn, applies. Thus the parameter is not passed at all to the template matching testNode.

My guess is that if you replace:

  <xsl:apply-templates> 
    <xsl:with-param name="testParam">TEST_PARAMETER</xsl:with-param> 
  </xsl:apply-templates> 

with

  <xsl:apply-templates select="testNode"> 
    <xsl:with-param name="testParam">TEST_PARAMETER</xsl:with-param> 
  </xsl:apply-templates> 

you could get the desired output.

Also, you could trace with an XSLT debugger (such as the one in Visual Studio) and see exactly which template is selected.

.3. Is there a better way to accomplish this?

As I said earlier, global parameters can be used as alternative -- I am not sure that this is better, though.

Finally, here is the code that I ran that cannot repro your problem:

XSLT stylesheet:

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

 <xsl:template match="/">
      This is text1
      <xsl:apply-templates>
        <xsl:with-param name="testParam">TEST_PARAMETER</xsl:with-param>
      </xsl:apply-templates>
      This is text2
 </xsl:template>

 <xsl:template match="testNode">
  <xsl:param name="testParam" />
  <xsl:value-of select="$testParam" />
 </xsl:template>
</xsl:stylesheet>

XML document:

<?xml-stylesheet type="text/xsl" href="main.xsl"?>
<testNode>
  <subNode/>
</testNode>

Result:

  This is text1
  TEST_PARAMETER
  This is text2

UPDATE:

The OP has provided more accurate information which prooves my guess.

Now it is obvious that the problem is caused by allowing the XSLT built-in template for element node to be selected for wrapperNode. This template, naturally, doesn't know about any parameters and it doesn't use the testParam parameter nor does it pass this parameter through. Thus, the <xsl:apply-templates/> in the built-in template causes the template matching testNode to be selected without passing any parameter to it. THis explains the reported behavior/result.

Solution: The solution is to specify a template matching wrapperNode that accepts a parameter named testParam and passes it through when it applies templates:

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

 <xsl:template match="/">
  <html>
    <body>
        <xsl:apply-templates>
          <xsl:with-param name="testParam" select="'TEST_PARAMETER'"/>
        </xsl:apply-templates>
    </body>
  </html>
 </xsl:template>

 <xsl:template match="testNode">
  <xsl:param name="testParam" />
  TEST1
  <xsl:value-of select="$testParam" />
  TEST2
 </xsl:template>

 <xsl:template match="wrapperNode">
  <xsl:param name="testParam" />

  <xsl:apply-templates>
   <xsl:with-param name="testParam" select="$testParam"/>
  </xsl:apply-templates>
 </xsl:template>
</xsl:stylesheet>

Now when this transformation is applied on the provided XML document, the expected result is produced:

<html>
<body>
  TEST1
  TEST_PARAMETER
  TEST2
 </body>
</html>
Characteristic answered 18/11, 2010 at 23:11 Comment(5)
Thank you for your time and knowledge with this issue. I have edited the question with a better, more accurate example. As I had mentioned before, everything else is working except the parameter. However, after reading your answer multiple times, something grabbed my attention: There is a node above testNode in the XML (named wrapperNode in the updated example) that has no template associated with it. Do you think this node is "eating" the parameter somewhere by default? Any ideas or suggestions? I really appreciate the help, and very detailed answer. +10 if I could.Northeastwards
@KevenK: Yes, this is exactly the reason for your problem. Add a template matching wrapperNode that has an xsl:param named testParam and in the body of this template have just: <xsl:apply-templates><xsl:with-param name="testParam" select="$testParam"/></xsl:apply-templates>Characteristic
Dimitre: Nailed it. Thank you so much for your help, I was unfamiliar with this behavior. The template was still being called, so I assumed that that apply-templates was the one calling it. I did not realize there was a default template that was consuming the parameter before calling my template. Thank you.Northeastwards
@KevenK: You are welcome. I updated my answer with the final solution and provided an explanation of the problem.Characteristic
Here is another interesting post about the tunnel parameters : [https://mcmap.net/q/588185/-xsl-passing-variables-between-templates]Washhouse
R
2

Your <html> and <body> elements can't occur where they are in an XSL stylesheet. When I remove them and the closing tags and run this in Oxygen/XML I get your "desired" output. I think you want to put those tags INSIDE the top-level template, in which case it would generate the output within html and body tags.

Which XSLT processor did you use that didn't complain about the invalid stylesheet?

Ruderal answered 18/11, 2010 at 22:6 Comment(1)
You may be right, and +1 for the observation, but unfortunately this is just a flaw in the example that I typed up for this question. As I had explicitly mentioned in the question, everything is working except the parameter. While your answer is correct, I'm afraid it doesn't help me with my issue (my fault for messing up the example, I suppose). I thank you for your time and effort though.Northeastwards
L
0

I ran the poster's 3 samples above as is and got this result using Notepad++ 7.7.1 on Wine under Ubuntu 18.04 with the XML Tools installed.

<html><body>

  TEST1
  TEST_PARAMETER
  TEST2

</body></html>
Licketysplit answered 12/7, 2019 at 20:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.