Bug #4640

Reference to a variable as an XPath step

Added by Ken Holman 30 days ago. Updated 17 days ago.

XPath conformance
Start date:
Due date:
% Done:


Estimated time:
Legacy ID:
Applies to branch:
10, trunk
Fix Committed on Branch:
10, trunk
Fixed in Maintenance Release:


In Saxon9HE this expression works fine using the first XPath step as an "if" statement:


In Saxon9HE the expression adds nothing to the result tree.

The $c:f is a tunnel parameter that is initialized with a global variable that is a massage of another global variable:

<xsl:variable name="c:CENcoverFormatting" as="document-node()">
    <xsl:for-each select="($c:mainFormattingDef)/*">
        <xsl:attribute name="{name(.)}"/>
        <xsl:copy-of select="@*"/>


    <xsl:with-param name="c:f" tunnel="yes" select="$c:CENcoverFormatting"/>


<block x:h="div*{name(.)}-caption" x:ignoreXHTML=""

Unfortunately, when I isolate the expression in a sample XSLT file, the expression works in both versions of Saxon. There is a 21-stylesheet-fragment tree acting on an XML file of 4300 lines where the problem came to light in the 17000 lines of output. I'm reluctant to package this all up in a submission.

Using the output does have x:figTableCaptionFonts="" and font-size="9pt":

Using the output doesn't have x:figTableCaptionFonts="" and font-size="9pt":

I'm guessing this is an optimization thingy, but I am but a user.

What would you have me put into my example to try and recreate the issue?

Thanks for your guidance!

. . . . . . Ken


#1 Updated by Ken Holman 30 days ago

(trying again!)

Using the output does have x:figTableCaptionFonts="" and font-size="9pt":

<block x:h="div*table-wrap-caption" x:ignoreXHTML="" text-align="center" x:figTableCaptionFonts="" font-size="9pt" space-after=".5em" space-after.conditionality="retain" font-weight="bold">

Using the output doesn't have x:figTableCaptionFonts="" and font-size="9pt":

<block x:h="div*table-wrap-caption" x:ignoreXHTML="" text-align="center" space-after=".5em" space-after.conditionality="retain" font-weight="bold">

#2 Updated by Michael Kay 30 days ago

I'm afraid it's impossible to diagnose this kind of issue unless there's something I can run. A small repro is ideal, but if you can't produce that then a big complicated repro will do. Without a repro (especially for cases of "wrong results" rather than a crash) there's basically no way we can investigate it.

#3 Updated by Ken Holman 30 days ago

Thanks, Mike. I was just looking for guidance on what to do with my test stylesheet to try and trigger the problem.

Go ahead and close the ticket and I'll code around the issue ... I'm probably not doing this very much in my work ... it is just very handy to do and it was working before. It was a real surprise when our product stopped working correctly and it has taken some hours to narrow it down and try and recreate it in a short test.

. . . . . Ken

#4 Updated by Michael Kay 30 days ago

You could try and see if it's an optimization problem by running with optimizations disabled - use the -opt flag on the command line. To drill down, you can suppress optimizations selectively (function inlining and loop lifting are the most usual culprits). Running with the -explain option gives a trace of which optimizations are being performed.

#5 Updated by Michael Kay 30 days ago

I'm wondering a little bit what this code is trying to do. Using a variable reference on the RHS of "/" is legitimate, but it's not very usual.


The semantics here are that you get as many instances of $c:f as there are nodes in self::table-wrap[ancestor::fig], and the duplicate nodes then get eliminated, so you end up with either zero or one. The expression is equivalent, I think, to

if (exists(self::table-wrap[ancestor::fig]) then $c:f/c:figTableCaptionFonts/@* else ()

So you could try that rewrite and see if it works.

Also related is that the type of the variable isn't declared (as far as I can see from the information given; if Saxon doesn't know statically whether the expression on the RHS of "/" is going to yield nodes or atomic values, then it has too generate code that can handle either -- and it will often dismantle this code later in the optimization process as more information becomes available, e.g. during function inlining). So adding a variable declaration could make a difference.

#6 Updated by Ken Holman 30 days ago

Yes, in fact my first sentence of this ticket reads "this expression works fine using the first XPath step as an "if" statement" and the cardinality of the RHS reference to $c:f is going to be 0..1 since the LHS evaluates to 0..1 along the self:: axis. I use this often, so it is not unusual in my stylesheets.

And, yes, when I already had changed it to your suggested if(), it worked fine. But, as you say, my request is legitimate. And I do this a number of times in my code because it is a handy expression. I have to find all my uses of this approach and replace them.

But it turns out that the bug is not that the expression is optimized out, but that it is optimized in! When I quoted the examples above, I got them mixed up because I was presuming the problem to be that attributes should have been added when, in fact, they should not have been added. I realized this when I had replaced the expression.

It turns out there is no fig ancestor and so the LHS should evaluate to () and the RHS should be ignored, but the RHS is being processed. Consider this unchanged code but adding a diagnostic attribute:

<block x:h="div*{name(.)}-caption" x:ignoreXHTML=""
<xsl:attribute name="x:debug" select="'DEBUG',c:xpath(.),exists(self::table-wrap[ancestor::fig]),self::table-wrap[ancestor::fig]/$c:f/c:figTableCaptionFonts/@*/name()"/>

... which produces this output:

<block x:h="div*table-wrap-caption" x:ignoreXHTML="" text-align="center" x:figTableCaptionFonts="" font-size="9pt"
  x:debug="DEBUG /standard/back[1]/app-group[1]/app[1]/sec[2]/sec[7]/sec[1]/list[1]/list-item[3]/p[2]/table-wrap[1] false x:figTableCaptionFonts x:figTableCaptionFonts font-size" space-after=".5em" space-after.conditionality="retain" font-weight="bold">

You can see that font-size= is not part of the literal result element, and the debug attribute reveals the exists() evaluates to false() and "fig" is not in the XPath address, and the copy of the faulty XPath expression again incorrectly enumerates the attributes.

So this shows that the xsl:copy-of copied the variable's attributes to the result tree even though the exists() indicates that the LHS does not exist.

And it happens in EE as well.

But still I have not been able to reduce the issue to a short example.

#7 Updated by Michael Kay 29 days ago

I guess it's probably a loop-lifting problem - the expression on the RHS being moved out of the implicit loop defined by the "/" operator because it has no functional dependency on the value of the expression on the left.

#8 Updated by Michael Kay 17 days ago

I have reproduced this as XSLT3 test case select-0302.

#9 Updated by Michael Kay 17 days ago

Well, it's definitely an optimization rewrite, but I'm having trouble seeing where it's happening - it's not logged by -explain. It doesn't seem to be the LoopLifter, nor does it seem to be SlashExpression.promoteFocusIndependentSubexpressions.

#10 Updated by Michael Kay 17 days ago

It's happening in DocumentSorter.optimize() where it's trying to avoid sort operations. The offending code is at line 142:

                // docOrder(A/B) can be rewritten as docOrder(B) in the case where B returns nodes
                // and is independent of the focus. We already know it returns nodes otherwise we wouldn't be here.
                if (!ExpressionTool.dependsOnFocus(rhs) &&
                        !rhs.hasSpecialProperty(StaticProperty.HAS_SIDE_EFFECTS) &&
                        rhs.hasSpecialProperty(StaticProperty.NO_NODES_NEWLY_CREATED)) {
                    return optimize(visitor, contextInfo);

I think that in this situation we should probably be rewriting docOrder(A/B) as head(A) ! docOrder(B).

#11 Updated by Michael Kay 17 days ago

  • Category set to XPath conformance
  • Status changed from New to Resolved
  • Assignee set to Michael Kay
  • Priority changed from Low to Normal
  • Applies to branch 10, trunk added
  • Fix Committed on Branch 10, trunk added

Fixed as proposed.

Please register to edit this issue

Also available in: Atom PDF