Применение временных деревьев № 1 — хранение статических данных в коде XSL-шаблона

Содержание

Задача.
Разобрать по косточкам теорию и практику использования временных деревьев в XSLT.

Иногда в коде шаблона нужно хранить константы. Например, для известной функции, занимающейся сменой регистра строки:

<!-- Да, на земле есть еще языки, но не будем горячиться -->
<xsl:variable
    name="UPPER_CASE"
    select="'ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'" />
<xsl:variable
    name="LOWER_CASE"
    select="'abcdefghijklmnopqrstuvwxyzабвгдеёжзийклмнопрстуфхцчшщъыьэюя'" />

<xsl:template name="change_case">
    <xsl:param name="input_string" />
    <xsl:param name="direction" select="'low'" />

    <xsl:choose>
        <xsl:when test="$direction = 'low'">
            <xsl:value-of select="translate($input_string, $UPPER_CASE, $LOWER_CASE)" />
        </xsl:when>
        <xsl:when test="$direction = 'up'">
            <xsl:value-of select="translate($input_string, $LOWER_CASE, $UPPER_CASE)" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$input_string" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Здесь константами являются простые строки с русским и латинским алфавитом в разных регистрах, сохраненные в переменные. А что делать, если в шаблоне хочется хранить не простую строку, а нечто более сложное, скажем, статический список чего-либо, который требуется обойти? Как водится, разберемся на примере.

Возьмем четырехкнопочный контрол размножения полей, который часто используется в сложных формах:

Пример контрола формы
Пример контрола формы

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

<xsl:template name="repeat_control">
    <div class="repeat_control">
        <input type="button" class="append">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'append_button']">
                        <xsl:value-of select="//label[@for = 'append_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>+<!-- плюс --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
        <input type="button" class="remove">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'remove_button']">
                        <xsl:value-of select="//label[@for = 'remove_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>?<!-- минус --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
        <input type="button" class="up">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'up_button']">
                        <xsl:value-of select="//label[@for = 'up_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>?<!-- вверх --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
        <input type="button" class="down">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'down_button']">
                        <xsl:value-of select="//label[@for = 'down_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>?<!-- вниз --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
    </div>
</xsl:template>

Мы последовательно вывели кнопки «Добавить», «Удалить», «Переместить вверх» и «Переместить вниз». Похожи эти четыре кнопки друг на друга, как близнецы-братья. Вот разве что «характеры» у них разные — различаются классами и лейблами. Количество кнопок и их классы — это чисто технические данные, являющиеся неотъемлемой частью самого шаблона, поэтому они должны храниться внутри XSL-файла.

Вот в такие моменты некоторые и кричат: «Ну и говно же этот ваш XSL! Да в любом языке программирования можно прямо в коде создать массив этих классов-лейблов и обойти его в цикле». Но XSL не таков, ему массивы чужды, поэтому приходится четыре раза писать одно и то же с точностью до статики. Ну что, научим его родину технолога любить? Сделаем-ка мы не массив, а временное дерево и перепишем этот кусок кода по-другому:

<xsl:variable name="repeat_control_buttons">
    <button name="append" label="+" />
    <button name="remove" label="?" />
    <button name="up" label="?" />
    <button name="down" label="?" />
</xsl:variable>
<xsl:variable
name="repeat_control_buttons_set"
select="exsl:node-set($repeat_control_buttons)" />

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

<xsl:template name="repeat_control">
    <div class="repeat_control">
        <xsl:for-each select="$repeat_control_buttons_set/button">
            <input type="button" class="{@name}">
                <xsl:attribute name="value">
                    <xsl:variable
                    name="custom_label"
                    select="$input_root//label[@for = concat(current()/@name, '_button')][1]" />
                    <xsl:choose>
                        <!-- Если есть специальный лейбл, выводим его -->
                        <xsl:when test="$custom_label">
                            <xsl:value-of select="$custom_label" />
                        </xsl:when>
                        <!-- Если нет – выводим лейбл по умолчанию -->
                        <xsl:otherwise>
                            <xsl:value-of select="@label" />
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:attribute>
            </input>
        </xsl:for-each>
    </div>
</xsl:template>

Вот и нет больше надоедливых повторов. Надо сказать, что избавиться от них можно было и более простым рефакторингом — выделением метода. Делаем шаблон с двумя параметрами name и label и вызываем его четыре раза, передав соответствующие строки. Тут, в общем-то, все средства хороши, и дело это разве что вкуса. Замечу лишь, что лично мне не нравятся громоздкие конструкции <xsl:with-param>, а у нас их тут будет по две штуки на каждый из четырех вызовов.

Также упомяну, что такое временное дерево можно получить, использовав функцию document('') с пустой строкой в аргументе. Рекомендую ознакомиться.

Я честно пытался пользоваться этой document(''), и оно даже работало. Однако очень быстро выяснилось, что именно этот вызов порождает непрерывный рост потребляемой памяти, как минимум на трансформаторе Xalan (о разных трансформаторах см. седьмую часть). Память постоянно росла, доходила до выделенного потолка, и сайт падал. Замена document('') на exsl:node-set($RTF) все исправила. Так что будьте осторожны с этой функцией.

В следующей части нас ждет не менее интересное применение временных деревьев — передача сложных параметров.

Временные деревья в XSLT

  1. Часть первая. Что такое временное дерево
  2. Часть вторая. Применение временных деревьев № 1 — хранение статических данных в коде XSL-шаблона
  3. Часть третья. Применение временных деревьев № 2 — передача сложных параметров
  4. Часть четвертая. Применение временных деревьев № 3 — шаблонизация однотипных строк
  5. Часть пятая. Применение временных деревьев № 4 — подстройка под готовый шаблон
  6. Часть шестая. Применение временных деревьев № 5 — многоступенчатая трансформация
  7. Часть седьмая. О трансформаторах, версиях XSLT и переносимости кода, используещего временные деревья
  8. Часть восьмая. О производительности при работе с временными деревьями
  9. Часть девятая. Тонкости использования временных деревьев


23 июня 2011 г.
Александр Самиляк
http://www.artlebedev.ru/tools/technogrette/xslt/temptrees-2/