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:template
s 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:template
s, 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:template
s.
The following is simplified example code (typed up for this question, it may contain a typo or two). I have many many different xsl:template
s 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 version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="main.xsl"?>
<wrapperNode>
<testNode>
<subNode/>
</testNode>
</wrapperNode>
<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>
<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>
TEST1 TEST2
TEST1 TEST_PARAMETER TEST2
My questions in regards to this:
Is it possible to send a named
parameter to all of my
xsl:template
s 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)?
What am I doing wrong with my current sample code that it does not seem to receive the parameter at all?
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.
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} .
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.
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.
The difference is that the value of an xsl:param could be set outside the context in which it is declared.
My questions in regards to this:
- 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>
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With