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

    HTML и CSSXSLTJavaScriptИзображенияСофтEtc
    Александр Самиляк

    23 июня 2011


    Задача.

    Разобрать по косточкам теорию и практику использования временных деревьев в 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>&#43;<!-- плюс --></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>&#8722;<!-- минус --></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>&#8593;<!-- вверх --></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>&#8595;<!-- вниз --></xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
        </input>
      </div>
    </xsl:template>
    

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

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

     <xsl:variable name="repeat_control_buttons">
      <button name="append" label="&#43;" />
      <button name="remove" label="&#8722;" />
      <button name="up" label="&#8593;" />
      <button name="down" label="&#8595;" />
    </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) все исправила. Так что будьте осторожны с этой функцией.


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

    1
    2
    3
    4
    5
    6
    7
    8
    9