Search code examples
xsltxslt-1.0minimum

How to select the smallest value from a bunch of variables?


Assume I have variables $a, $b, $c and $d which all hold numbers. I would like to get the smallest (largest) value. My typical XSLT 1.0 approach to this is

<xsl:variable name="minimum">
  <xsl:for-each select="$a | $b | $c | $d">
    <xsl:sort
      select="."
      data-type="number"
      order="ascending" />
    <xsl:if test="position()=1"><xsl:value-of select="." /></xsl:if>
  </xsl:for-each>
</xsl:variable>

However, my xslt 1.0 processor complains with

runtime error: file stylesheet.xslt line 106 element for-each
The 'select' expression does not evaluate to a node set.

How can I compute the minimum (maximum) of the given values?


Of course, I could use a long series of <xsl:when> statements and check all combinations, but I'd rather like a smaller solution.


Solution

  • If the variables have statically defined values (not dynamically computed), then something like the following can be done with XSLT 1.0:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="text"/>
    
     <xsl:variable name="vA" select="3"/>
     <xsl:variable name="vB" select="1"/>
     <xsl:variable name="vC" select="9"/>
     <xsl:variable name="vD" select="5"/>
    
     <xsl:template match="/">
         <xsl:for-each select=
          "document('')/*/xsl:variable
             [contains('|vA|vB|vC|vD|', concat('|', @name, '|'))]
               /@select
          ">
          <xsl:sort data-type="number" order="ascending"/>
    
          <xsl:if test="position() = 1">
           Smallest: <xsl:value-of select="."/>
          </xsl:if>
          <xsl:if test="position() = last()">
           Largest: <xsl:value-of select="."/>
          </xsl:if>
         </xsl:for-each>
     </xsl:template>
    </xsl:stylesheet>
    

    When this transformation is applied on any XML document (not used), the wanted, correct result is produced:

       Smallest: 1
       Largest: 9
    

    II. Now, suppose the variables are dynamically defined.

    We can do something like this (but need the xxx:node-set() extension function):

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:ext="http://exslt.org/common">
     <xsl:output method="text"/>
    
     <xsl:variable name="vA" select="number(/*/*[3])"/>
     <xsl:variable name="vB" select="number(/*/*[1])"/>
     <xsl:variable name="vC" select="number(/*/*[9])"/>
     <xsl:variable name="vD" select="number(/*/*[5])"/>
    
     <xsl:template match="/">
         <xsl:variable name="vrtfStore">
           <num><xsl:value-of select="$vA"/></num>
           <num><xsl:value-of select="$vB"/></num>
           <num><xsl:value-of select="$vC"/></num>
           <num><xsl:value-of select="$vD"/></num>
         </xsl:variable>
    
         <xsl:for-each select="ext:node-set($vrtfStore)/*">
          <xsl:sort data-type="number" order="ascending"/>
    
          <xsl:if test="position() = 1">
           Smallest: <xsl:value-of select="."/>
          </xsl:if>
          <xsl:if test="position() = last()">
           Largest: <xsl:value-of select="."/>
          </xsl:if>
         </xsl:for-each>
     </xsl:template>
    </xsl:stylesheet>
    

    when this transformation is applied on the following XML document:

    <nums>
      <num>01</num>
      <num>02</num>
      <num>03</num>
      <num>04</num>
      <num>05</num>
      <num>06</num>
      <num>07</num>
      <num>08</num>
      <num>09</num>
      <num>10</num>
    </nums>
    

    the wanted, correct result is produced:

       Smallest: 1
       Largest: 9