Перейти к содержанию

Включение условий в выражения if

Задача

У вас есть сложная XSLT-программа, которая оказалась излишне длинной из-за того, что запись условий if then else в XML слишком многословна.

Решение

XPath 1.0

В XPath 1.0 есть несколько приемов, позволяющих обойтись без громоздкого выражения xsl:choose в простых ситуациях. Они основаны на том, что в число вом контексте значение false преобразуется в 0, а значение true – в 1.

Так, например, минимум, максимум и абсолютную величину можно вычислить непосредственно. Ниже предполагается, что $x и $y – целые числа.

min: ($x <= $y) * $x + ($y < $x) * $y

max: ($x >= $y) * $x + ($y > $x) * $y

abs: (1 - 2 * ($x < 0)) * $x

XPath 2.0

Для описанных в предыдущем разделе операций (min, max, abs) в XPath 2.0 появились специальные функции. Чтобы реализовать другие простые условия, можно воспользоваться новой конструкцией – выражением if.

Вместо отсутствующего атрибута взять значение по умолчанию 10: if (@x) then @x else 10

Вместо отсутствующего элемента взять значение по умолчанию 'unauthorized': if (password ) then password else 'unauthorized'

Защита от деления на нуль: if ($d ne 0) then $x div $d else 0

Текст элемента para, если он содержит хотя бы один непробельный символ, иначе единственный пробел: if (normalize-space(para)) then string(para) else ' '

Обсуждение

Если вы давно работаете с XSLT 1.0, то, наверное, кривитесь всякий раз, как нужно включить в шаблон условно выполняемый код, По себе знаю, часто приходилось применять встроенные в XSLT средства сопоставления с образцом, лишь бы обойтись без условий. И дело не в том, что такой код сложнее или менее эффективен, просто он получается очень многословным. Простое условие xsl:if еще терпимо, но, если дело доходит до ветвления if then else, то приходится обращаться к громоздкой конструкции xsl:choose. Печально это.

В XSLT 2.0 появилась альтернатива, но она является частью XPath 2.0, а не самого языка XSLT. При первом знакомстве складывается впечатление, что XPath только испортили введением конструкции, которая применяется для управления логикой выполнения программы. Однако, начав активно использовать XPath 2.0, вы скоро поймете, что и XPath, и XSLT только выиграли от такого решения. К тому же условные выражения XPath 2.0 не отменяют конструкции xsl:if, а лишь уменьшают потребность в ней в тех ситуациях, когда она выглядит особенно неуклюже. В качестве иллюстрации рассмотрим такой фрагмент:

<!-- XSLT 1.0 -->
<xsl:variable name="size">
    <xsl:choose>
        <xsl:when test="$x > 3">большой</xsl:when>
        <xsl:otherwise>маленький</xsl:otherwise>
    </xsl:choose>
</xsl:variable>
<!-- XSLT 2.0 -->
<xsl:variable name="size" select="if ($x gt 3) then 'большой' else 'маленький'" />

Полагаю, что большинство читателей предпочтет второе решение, возможное в XSLT 2.0. Следует сделать важное замечание об условных выражениях в XPath: ветвь else обязательна. Программисты на языке C могут провести аналогию с тернарным выражением a ? b : c. Часто в случае, когда разумной альтернативы нет, используют пустую последовательность ().

Условные выражения удобны, когда нужно подставить значения по-умолчанию, а схемы, в которой они были бы определены, не существует.

Значение по умолчанию для необязательного атрибута: if (@optional) then @optional else 'some-default'

Значение по умолчанию для необязательного элемента: if (optional) then optional else 'some-default'

Также они оказываются полезны, чтобы избежать неопределенных или нежелательных результатов при вычислении выражений. В следующем примере у нас, вероятно, есть причина предпочесть в качестве результата нуль, а не бесконечность number('Infinity'): if ($divisor ne 0) then $dividend div $divisor else 0

Условия могут и более сложными. В следующем коде производится перекодировка списка из нескольких значений:

if (size eq 'XXL') then 50
else if (size eq 'XL') then 45
else if (size eq 'L') then 40
else if (size eq 'M') then 34
else if (size eq 'S') then 32
else if (size eq 'XS') then 29
else -1

Однако в данном случае предпочтительнее решение, основанное на последовательностях, особенно если заменить литеральные строки переменными, которые читаются из внешнего XMLфайла: (50,45,40,34,32,29,-1)[(index-of(('XXL',’XL’,’L’,’M’,S’,’XS’)), size),7)[1]]

Здесь предполагается, что у контекстного узла есть единственный дочерний элемент с именем size, в противном случае выражение окажется некорректным (хотя это можно исправить, написав size[1]). Мы также полагаемся на то, что функция index-of возвращает пустую последовательность, если элемент не найден. Именно для этого мы добавили заведомо отсутствующий элемент 7, реализовав тем самым поведение ветви else.