xmlxsltsaxon-js

XSLT - variable in condition is not assigned with array value


Using xslt3 (Saxon JS) and not bound to a specific XSLT version

Given this sample.xml

<?xml version=”1.0″ encoding=”UTF-8″?>
<tasks>
    <task id="id0"/>
    <task id="id1" parent="#id0" deps="#id2 #id3 #id0"/>
    <task id="id2" parent="#id0"/>
    <task id="id3" parent="#id0"/>
</tasks>

and this sample.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:for-each select="/tasks/task">
            <xsl:variable name="parentTask" select="replace(@parent,'#','')"/>
            
            <xsl:variable name="depTasks">
                <xsl:choose>
                    <xsl:when test="@deps">

                        <xsl:value-of select="tokenize(replace(@deps,'#',''),'\s')"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$parentTask"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>

            <parent id="{@id}">
                <xsl:for-each select="/tasks/task[@id=$depTasks]">
                    <dep>
                        <xsl:value-of select="@id"/>
                    </dep>
                </xsl:for-each>
            </parent>

        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

I would expect the $depTasks variable to contain an array (all the deps listed in the deps attribute) or a single value (the dep listed in the parent attribute), and obtain a transformation like this:

<?xml version="1.0" encoding="UTF-8"?>
<parent id="id0"/>
<parent id="id1">
    <dep>id2</dep>
    <dep>id3</dep>
    <dep>id0</dep>
</parent>
<parent id="id2">
    <dep>id0</dep>
</parent>
<parent id="id3">
    <dep>id0</dep>
</parent>

BUT instead I'm getting this result (it seems that only the first element of the tokenization is assigned)

<?xml version="1.0" encoding="UTF-8"?>
<parent id="id0"/>
<parent id="id1">
    <dep>id2</dep>
    <!-- MISSING DEPS -->
</parent>
<parent id="id2">
    <dep>id0</dep>
</parent>
<parent id="id3">
    <dep>id0</dep>
</parent>

Do problems or limitations exist for variable assigment inside conditions? Or am I missing something here?

Note that, if I change a bit the stylesheet and declare the variable OUTSIDE a condition, an array is assigned as I expect.

sample2.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:for-each select="/tasks/task">
            <xsl:variable name="parentTask" select="replace(@parent,'#','')"/>
            <!-- VARIABLE assignement outside a condition: value is an array -->
            <xsl:variable name="depTasks" select="tokenize(replace(@deps,'#',''),'\s')"/>

            <parent id="{@id}">
                <xsl:for-each select="/tasks/task[@id=$depTasks]">
                    <dep>
                        <xsl:value-of select="@id"/>
                    </dep>
                </xsl:for-each>
            </parent>

        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

resulting in this transformation

<?xml version="1.0" encoding="UTF-8"?>
<parent id="id0"/>
<parent id="id1">
    <dep>id0</dep>
    <dep>id2</dep>
    <dep>id3</dep>
</parent>
<parent id="id2"/>
<parent id="id3"/>

Solution

  • Your are not using any arrays in your code at all. In

          <xsl:variable name="depTasks">
                <xsl:choose>
                    <xsl:when test="@deps">
    
                        <xsl:value-of select="tokenize(replace(@deps,'#',''),'\s')"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$parentTask"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
    

    you are creating a result tree fragment containing a text node with the string value of the tokenize call or the $parentTask variable.

    In <xsl:variable name="depTasks" select="tokenize(replace(@deps,'#',''),'\s')"/> the value of the variable is the result of the tokenize call which is a sequence of string values.

    If for the previous approach, although not recommeded, you wanted a sequence of string, you would need to use

    <xsl:variable name="depTasks" as="xs:string*">
      ...
      <xsl:sequence select="tokenize(...)"/>
    
      ...
    
    </xsl:variable>
    

    The preferred way for the more complex part would be select="if (@deps) then tokenize(...) else $parentTask".