Search code examples
xsltxslt-1.0

Using a variable that has a choose statement as a boolean


In XSLT 1.0, I'm creating a variable that has a choose statement within it. The purpose for this would be handle logic once in a variable. And then use the variable as a boolean downstream one or more times to control the logic flow. The issue is that whether the logic within the variable evaluates to true() or false(), the variable always evaluates to true() in a test construct.

So, can I create a boolean variable in this manner? And, if so, how should I do it and use it downstream?

My input XML is the following:

<root>
  <box id="435" type="A">keep</box>
  <box  id="872" type="A"></box>
  <box id="903" type="B">keep</box>
  <box id="1051" type="A">keep</box>
</root>

My expected result is the same:

<root>
  <box id="435" type="A">keep</box>
  <box  id="872" type="A"></box>
  <box id="903" type="B">keep</box>
  <box id="1051" type="A">keep</box>
</root>

But my actual result is:

<root>
  <box id="435" type="A">keep</box>

  <box id="903" type="B">keep</box>
  <box id="1051" type="A">keep</box>
</root>

My attempt at programming is:

<xsl:key name = "myKey" match="box" use="local-name()"/>

<xsl:variable name="allMustMatch" select="'true'"/>

<xsl:variable name="processFlag">
  <xsl:choose>
    <xsl:when test="$allMustMatch = 'true'">
      <xsl:variable name="allCount" select="count(key('myKey', 'box'))"/>
      <xsl:variable name="typeCount" select="count(key('myKey', 'box')[not(@type = preceding::box/@type)]) + 1"/>
      <xsl:value-of select="$allCount = $typeCount"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="true()"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:variable>

<xsl:template match="box">
  <xsl:choose>
    <!--  When $processFlag is false(), the following test evaluates to true().
          Also, when boolean($processFlag) is false(), the test evaluates to true(). 
          And when $processFlag is true() the test evaluates to true().
          How can I use $processFlag as a boolean? Or can I? -->
    <xsl:when test="boolean($processFlag)">
      <xsl:if test="normalize-space(.) != ''">
        <xsl:copy>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<!-- Identity template. -->
<xsl:template match="node()|@*">
  <xsl:copy>
    <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
</xsl:template>

Solution

  • The XSLT specification provides two ways to define a variable (actually three, but we'll skip that for now):


    The first way is using the select attribute. Here the variable will be bound directly to the selected object, and both its data type and its Boolean value will be that of the selected object.

    For example, the data type of a variable defined as:

    <xsl:variable name="myVar" select="1" />
    

    will be number and its Boolean value will be true unless the number is 0 or NaN.


    The data type of a variable defined as:

    <xsl:variable name="myVar" select="1=0" />
    

    will be boolean (and of course, its Boolean value will be false).


    The data type of a variable defined as:

    <xsl:variable name="myVar" select="/" />
    

    will be a node-set and its Boolean value will be true unless the node-set is empty - for example, the Boolean value of:

    <xsl:variable name="myVar" select="/.." />
    

    will be false because the root node has no parent.


    Note that defining a variable as you did:

    <xsl:variable name="allMustMatch" select="'true'"/>
    

    binds the variable to a string, and both this and:

    <xsl:variable name="allMustMatch" select="'false'"/>
    

    wil evaluate as true in a Boolean test, because any string with a non-zero length is evaluated as true. The correct way to hardcode a variable to a Boolean value would be:

    <xsl:variable name="allMustMatch" select="true()"/>
    

    or:

    <xsl:variable name="allMustMatch" select="false()"/>
    


    The other way to define a variable is by using a template. This is necessary if, for example, you need to derive the value of the variable by using xsl:choose (as in your question). Here the data type of the variable will be a result tree fragment - and its Boolean value will be always true, regardless of the contents of the template. This is because the RTF is equivalent to a node-set, and it will always contain a root node, with the nodes produced by the template as child nodes - therefore it will never be empty.

    So the answer to your question "can I create a boolean variable in this manner" is no (at least not in XSLT 1.0). The most you can do is something like:

    <xsl:variable name="myVar">
        <xsl:choose>
            <xsl:when test="$x=$y">1</xsl:when>
            <xsl:otherwise>0</xsl:otherwise>
        </xsl:choose>
    </xsl:variable> 
    

    and then, when you need to evaluate the variable as a Boolean, do:

    <xsl:if test="number($myVar)">
    

    However, I am not convinced you need such elaborate construct in your example. I am not sure I follow the required logic here, but you could probably do something like (untested):

    <xsl:variable name="allMustMatch" select="true()"/>
    <xsl:variable name="allCount" select="count(key('myKey', 'box'))"/>
    <xsl:variable name="typeCount" select="count(key('myKey', 'box')[not(@type = preceding::box/@type)]) + 1"/>
    <xsl:variable name="processFlag" select="$allCount=$typeCount or not($allMustMatch)"/>