Как выбрать наименьшее значение из множества переменных?

Предположим, у меня есть переменные $a, $b, $c и $d, все из которых содержат числа. Я хотел бы получить наименьшее (наибольшее) значение. Мой типичный подход XSLT 1.0 к этому таков:

<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>

Однако мой процессор xslt 1.0 жалуется на

ошибка выполнения: файл stylesheet.xslt, строка 106, элемент для каждого
Выражение 'select' не соответствует набору узлов.

Как я могу вычислить минимум (максимум) заданных значений?


Конечно, я мог бы использовать длинную серию операторов <xsl:when> и проверять все комбинации, но я бы предпочел меньшее решение.


person bitmask    schedule 27.04.2012    source источник
comment
Основная проблема, я думаю, в том, что числа не являются узлами.   -  person cHao    schedule 27.04.2012
comment
Я думаю, что основная проблема заключается в том, что в XSLT 1.0 выражение в select должно оцениваться как набор узлов. (w3.org/TR/xslt#for-each)   -  person Daniel Haley    schedule 27.04.2012


Ответы (3)


Если переменные имеют статически определенные значения (не вычисляемые динамически), то с помощью 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>

Когда это преобразование применяется к любому XML-документу (не используемому), создается желаемый правильный результат:

   Smallest: 1
   Largest: 9

II. Теперь предположим, что переменные определены динамически.

Мы можем сделать что-то подобное (но нужна функция расширения xxx:node-set()):

<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>

когда это преобразование применяется к следующему XML-документу:

<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>

получен желаемый правильный результат:

   Smallest: 1
   Largest: 9
person Dimitre Novatchev    schedule 28.04.2012
comment
Итак, document('') указывает на сам XSLT xml? Придется ли его заново разбирать? Еще один необычный трюк, который я изучил, спасибо. +1 - person Pavel Veller; 28.04.2012
comment
@PavelVeller: Да, это часто используется, если вы хотите иметь встроенные данные в самой таблице стилей XSLT, а не в отдельном файле. Да, повторный анализ выполняется во время выполнения, и это не слишком эффективно. Возможно, вам будет интересно увидеть и вторую часть решения. - person Dimitre Novatchev; 28.04.2012
comment
Вторая часть помогла, спасибо. Мои переменные сами по себе являются минимумом более сложных выражений в дереве ввода, поэтому, конечно, не статичны. - person bitmask; 28.04.2012

Это решение XSLT 1.0 использует рекурсивные шаблоны для синтаксического анализа списка значений с разделителями, чтобы вернуть минимальное/максимальное значение из списка.

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

   <xsl:variable name="a" select="'3'"/>
    <xsl:variable name="b" select="'1'"/>
    <xsl:variable name="c" select="'9'"/>
    <xsl:variable name="d" select="'5'"/>

    <xsl:template match="/">
        <xsl:text>&#xa;Smallest: </xsl:text>
        <xsl:call-template name="min">
            <xsl:with-param name="values" select="concat($a,',',$b,',',$c,',',$d)"/>    
        </xsl:call-template>

        <xsl:text>&#xa;Largest: </xsl:text>
        <xsl:call-template name="max">
            <xsl:with-param name="values" select="concat($a,',',$b,',',$c,',',$d)"/>    
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="min">
        <xsl:param name="values" />
        <xsl:param name="delimiter" select="','"/>
        <xsl:param name="min"/>

        <xsl:variable name="currentValue" >
            <xsl:choose>
                <xsl:when test="contains($values, $delimiter)">
                    <xsl:value-of select="substring-before($values,$delimiter)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$values"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

        <xsl:variable name="minimumValue">
            <xsl:choose>
                <xsl:when test="$min and $min > $currentValue">
                    <xsl:value-of select="$currentValue"/>
                </xsl:when>
                <xsl:when test="$min">
                    <xsl:value-of select="$min"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$currentValue" />
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

       <xsl:choose>
            <xsl:when test="substring-after($values,$delimiter)">
                <xsl:call-template name="min">
                    <xsl:with-param name="min" select="$minimumValue" />
                    <xsl:with-param name="values" select="substring-after($values,$delimiter)" />
                    <xsl:with-param name="delimiter" select="$delimiter"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$minimumValue" />
            </xsl:otherwise>
        </xsl:choose>                
    </xsl:template>


    <xsl:template name="max">
        <xsl:param name="values" />
        <xsl:param name="delimiter" select="','"/>
        <xsl:param name="max"/>

        <xsl:variable name="currentValue" >
            <xsl:choose>
                <xsl:when test="contains($values, $delimiter)">
                    <xsl:value-of select="substring-before($values,$delimiter)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$values"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

        <xsl:variable name="maximumValue">
            <xsl:choose>
                <xsl:when test="$max and $currentValue > $max">
                    <xsl:value-of select="$currentValue"/>
                </xsl:when>
                <xsl:when test="$max">
                    <xsl:value-of select="$max"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$currentValue" />
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

        <xsl:choose>
            <xsl:when test="substring-after($values,$delimiter)">
                <xsl:call-template name="max">
                    <xsl:with-param name="max" select="$maximumValue" />
                    <xsl:with-param name="values" select="substring-after($values,$delimiter)" />
                    <xsl:with-param name="delimiter" select="$delimiter"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$maximumValue" />
            </xsl:otherwise>
        </xsl:choose>                
    </xsl:template>
</xsl:stylesheet>

При выполнении выдает следующий вывод:

Smallest: 1
Largest: 9
person Mads Hansen    schedule 28.04.2012

Мне никогда не приходилось делать это в 1.0 (я использую 2.0), но вы можете сделать это:

  <xsl:variable name="minimum">
    <xsl:choose>
      <xsl:when test="$b > $a and $c > $a and $d > $a"><xsl:value-of select="$a"/></xsl:when>
      <xsl:when test="$a > $b and $c > $b and $d > $b"><xsl:value-of select="$b"/></xsl:when>
      <xsl:when test="$b > $c and $a > $c and $d > $c"><xsl:value-of select="$c"/></xsl:when>
      <xsl:when test="$b > $d and $c > $d and $a > $d"><xsl:value-of select="$d"/></xsl:when>
    </xsl:choose>
  </xsl:variable>

Хотя должен быть лучший способ.

person Daniel Haley    schedule 27.04.2012
comment
Точно мое мышление. Как я уже сказал, у меня уже есть план на случай непредвиденных обстоятельств. Но это нехорошо, и это, конечно, не масштабируется. - person bitmask; 27.04.2012
comment
@bitmask - я полностью пропустил последний абзац вашего вопроса, где вы сказали, что можете это сделать. Надеюсь, у кого-то (@DimitreNovatchev, вероятно) будет гораздо лучшее решение. - person Daniel Haley; 27.04.2012