You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@xalan.apache.org by Jens Lautenbacher <jt...@schlund.de> on 2001/03/05 15:38:13 UTC

Xalan-J 2 Bug: scoping of xsl:param wrong???

Hi, with this xml file:

<?xml version="1.0" encoding="iso-8859-1"?>
<b>
  <c>
    <a>One</a>
    <a>Two</a>
    <a>Three</a>
  </c>
  <c>
    <a>Four</a>
    <a>Five</a>
    <a>Six</a>
  </c>
</b>


and this XSL file:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="ISO-8859-1" indent="yes"/>

  <xsl:template match="b">
    <xsl:apply-templates/>
    <xsl:for-each select="./c/a">
      <xsl:call-template name="a">
        <xsl:with-param name="num"><xsl:value-of select="count(preceding-sibling::a) + 1"/></xsl:with-param>
      </xsl:call-template>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template match="a" name="a">
    <xsl:param name="num"><xsl:value-of select="./text()"/></xsl:param>
    * <xsl:value-of select="$num"/> (<xsl:value-of select="./text()"/>)
  </xsl:template>
</xsl:stylesheet>


results in the following output (empty lines deleted):

    * One (One)
    * One (Two)
    * One (Three)
    * Four (Four)
    * Four (Five)
    * Four (Six)
    * 1 (One)
    * 2 (Two)
    * 3 (Three)
    * 1 (Four)
    * 2 (Five)
    * 3 (Six)

I would have expected that the scope of a param declaration is limited
to the template it is in, and another call of the template during the
same xsl:apply-templates wouldn't see the previous value. The expected
output would be (and saxon and xp do it like this):

    * One (One)
    * Two (Two)
    * Three (Three)
    * Four (Four)
    * Five (Five)
    * Six (Six)
    * 1 (One)
    * 2 (Two)
    * 3 (Three)
    * 1 (Four)
    * 2 (Five)
    * 3 (Six)

Is this a known bug? Thanx in advance,

        jtl

Re: Xalan-J 2 Bug: scoping of xsl:param wrong???

Posted by Jens Lautenbacher <jt...@schlund.de>.
Jens Lautenbacher <jt...@schlund.de> writes:

> Is this a known bug? Thanx in advance,
> 
>         jtl

Hi, I digged a little bit around in the source but didn't came far
with my limited experience... Is it a VariableStack problem? The first
time after a xsl:apply-templates is called, the first <a> template is
called and the xsl:param will get initialized with the stuff it
contains. somehow this value seems to be put into the wrong
environment (wrong position on the stack????) so in the next call to
<a> it will look as if the parameter value is already on the stack.

Sorry if this is plain stupid, but I ddidn't manage to get any
further. Any response if this is a known problem, and if not, how it
may be fixed would be very much appreciated.

        jtl


Re: Xalan-J 2 Bug: scoping of xsl:param wrong???

Posted by Gary L Peskin <ga...@firstech.com>.
Jens --

This is definitely a bug and I haven't seen it reported before.  I'm
looking into it and will advise.

Gary

Jens Lautenbacher wrote:
> 
> Hi, with this xml file:
> 
> <?xml version="1.0" encoding="iso-8859-1"?>
> <b>
>   <c>
>     <a>One</a>
>     <a>Two</a>
>     <a>Three</a>
>   </c>
>   <c>
>     <a>Four</a>
>     <a>Five</a>
>     <a>Six</a>
>   </c>
> </b>
> 
> and this XSL file:
> 
> <?xml version="1.0" encoding="iso-8859-1"?>
> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
>   <xsl:output method="xml" encoding="ISO-8859-1" indent="yes"/>
> 
>   <xsl:template match="b">
>     <xsl:apply-templates/>
>     <xsl:for-each select="./c/a">
>       <xsl:call-template name="a">
>         <xsl:with-param name="num"><xsl:value-of select="count(preceding-sibling::a) + 1"/></xsl:with-param>
>       </xsl:call-template>
>     </xsl:for-each>
>   </xsl:template>
> 
>   <xsl:template match="a" name="a">
>     <xsl:param name="num"><xsl:value-of select="./text()"/></xsl:param>
>     * <xsl:value-of select="$num"/> (<xsl:value-of select="./text()"/>)
>   </xsl:template>
> </xsl:stylesheet>
> 
> results in the following output (empty lines deleted):
> 
>     * One (One)
>     * One (Two)
>     * One (Three)
>     * Four (Four)
>     * Four (Five)
>     * Four (Six)
>     * 1 (One)
>     * 2 (Two)
>     * 3 (Three)
>     * 1 (Four)
>     * 2 (Five)
>     * 3 (Six)
> 
> I would have expected that the scope of a param declaration is limited
> to the template it is in, and another call of the template during the
> same xsl:apply-templates wouldn't see the previous value. The expected
> output would be (and saxon and xp do it like this):
> 
>     * One (One)
>     * Two (Two)
>     * Three (Three)
>     * Four (Four)
>     * Five (Five)
>     * Six (Six)
>     * 1 (One)
>     * 2 (Two)
>     * 3 (Three)
>     * 1 (Four)
>     * 2 (Five)
>     * 3 (Six)
> 
> Is this a known bug? Thanx in advance,
> 
>         jtl

Re: Xalan-J 2 Bug: scoping of xsl:param wrong???

Posted by Gary L Peskin <ga...@firstech.com>.
Jens Lautenbacher (jtl@schlund.de) has pointed up a problem with
xsl:param and I have a minimal case to recreate the problem.

XML:

<?xml version="1.0" encoding="iso-8859-1"?>
<b>
  <a>One</a>
  <a>Two</a>
</b>

XSL:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

  <xsl:template match="a">
    <xsl:param name="num"><xsl:value-of select="./text()"/></xsl:param>
    * <xsl:value-of select="$num"/> (<xsl:value-of select="./text()"/>)
  </xsl:template>
</xsl:stylesheet>

This results in the following output (ignoring blank lines):

    * One (One)
    * One (Two)

It should result in the following output (again, ignoring blank lines):

    * One (One)
    * Two (Two)

I believe that I have found the problem but I'd like to summarize how I
understand the variable/parameter mechanism works.  This has been very
confusing to me and, although I've seen comments in the commit message
from Scott, I'd like to make sure that my understanding here is correct.

When an apply-templates or call-template instruction is executed, a new
Frame is created on the VariableStack to represent the invoked
template.  Any with-param children are pushed onto the new Frame and
flagged with "isParamVar" set to true to indicate that these are
parameters.

As the invoked template is being executed, if we find a reference to a
variable, we search the Frame, ignoring any entries with isParamVar set
to true.  We ignore those entries because these represent parameter
values passed by apply-templates or call-template and cannot be
referenced in the invoked template unless they have been "received" with
an xsl:param element at the beginning of the template.

If we encounter an xsl:param element in the invoked template, we search
the Frame for entries for which isParamVar is true.  This means that we
have found a value that was passed with an apply-templates or a
call-template and we can now "receive" it.  If we find such a value, we
change isParamVar to false so that it can now be referenced like a
regular old variable.  If we don't find such a value, we evaluate the
default value supplied by the select expression or by the children of
xsl:param and that becomes a real value so we push that onto the Frame
and set isParamVar to false so that this will be referencable as a
variable.

If we encounter an xsl:variable element in the invoked template, we push
that value onto the Frame and mark it with isParamVar as false.  In this
way, it can be referenced as a variable when future references come
along.

So far, so good.  The problem comes now when we have an apply-templates
with a node-set and we want to now invoke the target template for the
next node in the node-set.  In the above example, this is the node with
the element <a> that has the child "Two".

To expedite handling of this, I think, the code issues a reMarkParams()
in ElemForEach.transformSelectedNodes().  This has the effect of marking
everything as a passed parameter so that it will not be seen as a
variable yet until the xsl:param or xsl:variable element is processed
again.  I think the point of this was to prevent the derefencing and
subsequent reconstruction of Args in the Frame when we know that they're
going to be needed again for this second go-round.  Is that the
rationale for this approach?

Is my explanation correct so far?

The problem is that the default value pushed as a result of evaluating
the xsl:param the first time is not really a passed parameter even
though reMarkParams() marks it as such.  So, when xsl:param comes in the
second time around, it finds the "passed" parameter and uses that value
instead of re-evaluating the default value.

I hope this wasn't too elementary but I'm a simple person and need to
take these things in small steps.

My proposed solution, which I'm happy to implement and test is to change
isParamVar to isVisible, along with the associated methods.  To me, this
is a clearer indication of what this is meant to do.  Then, we need to
add another flag isFromWithParam and set this flag to true if the Arg is
pushed onto the Frame when we push the parameters from xsl:with-param
and false otherwise.  In reMarkParams(), we would now reset isVisible()
to false, just like we do now.  However, when looking in the stack for
Variables, we would only pick up those with isVisible set to true.  When
looking for Parameters, we would only pick up those with isFromWithParam
set  to true.  I'll have to monkey with this and make sure that when we
push the new value of num onto the Frame, we've overwritten the old
value.

I haven't worked out all of the details but this is my general
thinking.  I hate adding another field to Arg but it beats clearing out
the Frame, repushing the parameters, and starting all over again in
terms of object creation and deletion.

Any thoughts?

Gary