tags:

views:

114

answers:

3

I am using the .Net XslCompiledTranform to run some simple XSLT (see below for a simplified example).

The example XSLT is meant to do simply show the value of the parameter that is passed in to the template. The output is what I expect it to be (i.e.

<result xmlns:p1="http://www.doesnotexist.com"&gt;
  <valueOfParamA>valueA</valueOfParamA>
</result>

when I use Saxon 9.0, but when I use XslCompiledTransform (XslTransform) in .net I get

<result xmlns:p1="http://www.doesnotexist.com"&gt;
  <valueOfParamA></valueOfParamA>
</result>

The problem is that that the parameter value of paramA is not being passed into the template when I use the .Net classes. I completely stumped as to why. when I step through in Visual Studio, the debugger says that the template will be called with paramA='valueA' but when execution switches to the template the value of paramA is blank.

Can anyone explain why this is happening? Is this a bug in the MS implementation or (more likely) am I doing something that is forbidden in XSLT?

Any help greatly appreciated.

This is the XSLT that I am using

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:extfn="http://exslt.org/common"  exclude-result-prefixes="extfn" xmlns:p1="http://www.doesnotexist.com"&gt;
<!-- 
    Replace msxml with
    xmlns:extfn="http://exslt.org/common" 
    xmlns:extfn="urn:schemas-microsoft-com:xslt" 
 -->
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
    <xsl:variable name="resultTreeFragment">
        <p1:foo>
        </p1:foo>
    </xsl:variable>
    <xsl:variable name="nodeset" select="extfn:node-set($resultTreeFragment)"/>
    <result>
        <xsl:apply-templates select="$nodeset" mode="AParticularMode">
            <xsl:with-param name="paramA" select="'valueA'"/>
        </xsl:apply-templates>
    </result>
</xsl:template>

<xsl:template match="p1:foo" mode="AParticularMode">
    <xsl:param name="paramA"/>

    <valueOfParamA>
        <xsl:value-of select="$paramA"/>
    </valueOfParamA>
</xsl:template>
</xsl:stylesheet>
A: 

Well after more experimenting I found that altering the apply-templates to

 <xsl:apply-templates select="$nodeset/*" mode="AParticularMode">
        <xsl:with-param name="paramA" select="'valueA'"/>
 </xsl:apply-templates>

(note the select="$nodeset/*" instead of select="nodeset") made it work as I wanted it to in .Net and Saxon.

I would however still be grateful if someone can explain why my first attempt failed.

Thanks in advance.

Chris F
A: 

Answer for your question,

Why my first attempt failed?

As you are using node-set() comfortably in your code I guess you might be well aware of Result Tree Fragment. If not, then go through this link.

Well. By taking advantage of RTF[Result tree fragment] you are able to treat "foo" as a node.

The variable $nodeset has stored the tree-structure of the node, so that you can treat it's value as node-set, where as variable $nodeset is still a variable. If you want to apply-template then apply on, it's child nodes[precisely elements] appearing as it's value,

Instead of * you could have used,

<xsl:apply-templates select="$nodeset/p1:foo" mode="AParticularMode">

This is more precise,

infant programmer
I can see what you are saying regarding the return value from the node-set function. I assumed that $nodeset would look like this $nodeset <p1:foo />but actually it is this however there are still 2 WTFs as I see it: -a) if
Chris F
I can see what you are saying regarding the return value from the node-set function. I assumed that the value of $nodeset be the p1:foo element but it is not - it is a node with p1:foo as a child. However there are still a WTF as I see it: -If $nodeset is indeed a node with p1:foo as a child, why did `select="$nodeset"` match it as p1:foo (because my template was invoked).Or to put it another way, why does `apply-templates select="$nodeset"` **and** `apply-templates select="$nodeset/*"` both invoke my template - I would have expected only 1 to invoke the template not both.
Chris F
yup, very confusing/complicated question :-P,Even I am intended to know the technical reason behind .. we'll keep this question "hot" for 2-3 days, ;-)
infant programmer
You are *close* to the truth, but not exactly :) See the explanation in my answer.
Dimitre Novatchev
+2  A: 

There is nothing strange -- this is the expected behavior of any XSLT 1.0 -compliant processor.

Explanation: The $nodeset variable defined as:

<xsl:variable name="nodeset" select="extfn:node-set($resultTreeFragment)"/>

in XSLT 1.0 contains a complete xml document -- a document node, denoted in XPath 1.0 by / .

Therefore,

<xsl:apply-templates select="$nodeset" mode="AParticularMode">
  <xsl:with-param name="paramA" select="'valueA'"/>
</xsl:apply-templates>

Will aplly a template matching the tree (the document node /) in the specified mode, if such template exists. In your case no such template exists. Therefore the built-in XSLT 1.0 template for / is applied (which belongs to every mode).

The text of the built-in template can be found in the spec:

<xsl:template match="*|/">
  <xsl:apply-templates/>
</xsl:template>

As per spec: "There is also a built-in template rule for each mode, which allows recursive processing to continue in the same mode in the absence of a successful pattern match by an explicit template rule in the stylesheet. This template rule applies to both element nodes and the root node. The following shows the equivalent of the built-in template rule for mode m.

<xsl:template match="*|/" mode="m">
  <xsl:apply-templates mode="m"/>
</xsl:template>

"

Of course, the built-in template doesn't know anything about your parameter $paramA and it doesn't pass it down to the applied templates.

Thus, finally, your template matching p1:foo" in mode="AParticularMode" is selected for processing. Nothing is passed as value for the parameter, so it has no value -- thus the <xsl:value-of> doesn't produce even a single character or node.

To correct this problem, simply add a template matching / and in mode "AParticularMode":

<xsl:template match="/" mode="AParticularMode">
  <xsl:param name="paramA"/>

  <xsl:apply-templates mode="AParticularMode">
    <xsl:with-param name="paramA" select="$paramA"/>
  </xsl:apply-templates>
</xsl:template>

and now you get the desired result.

In XSLT 2.0 (Saxon 9) you observe different behavior, because the default type of a variable is the most generic one: item()* and not a document-node().

To observe the same behavior in XSLT 2.0, simply specify the type of the xsl: variable : as="document-node()".

Dimitre Novatchev
Thank you Dimitre for your very clear explanation. I am a noob at XSLT and was unaware that there is a built-in template for each mode (every time I think I am getting to know XSLT I find another gaping hole in my knowledge). I assume that things are different in XSLT 2.0 and this is why I get different results in Saxon 9.0.
Chris F
@ChrisF, Yes, I updated my answer with explanation of the XSLT 2.0 behavior.
Dimitre Novatchev
@Dimitre, I was damn sure, that you know the answer .. Nice to see you :-)
infant programmer