Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSL named parameter 'with-param' using 'apply-templates'

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.

like image 897
KevenK Avatar asked Nov 18 '10 21:11

KevenK


People also ask

How do you pass a variable in an xsl template?

You are applying templates selecting folders , but have a template matching on folder . Either change it to folder , or if you have a folders template make sure that it passes the var1 parameter value down to the folder template. Your with-param @select uses '{var}' , which selects that literal string {var} .

What does xsl apply-templates select * /> mean?

Definition and Usage The <xsl:apply-templates> element applies a template to the current element or to the current element's child nodes. If we add a select attribute to the <xsl:apply-templates> element it will process only the child element that matches the value of the attribute.

What is xsl with-param?

The xsl:with-param element is used to set the explicit value of a named parameter when using the xsl:apply-templates and the xsl:call-template elements. The concept is that the xsl:param element is used to declare a local or global parameter by assigning a name and a default value.

What is difference between param and variable in XSLT?

The difference is that the value of an xsl:param could be set outside the context in which it is declared.


2 Answers

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>
like image 125
Dimitre Novatchev Avatar answered Oct 08 '22 15:10

Dimitre Novatchev


Your <html> and <body> tags 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 engine did you use that didn't complain about the invalid stylesheet?

like image 37
Jim Garrison Avatar answered Oct 08 '22 13:10

Jim Garrison