• Техногрет
  • Применение временных деревьев № 5 — многоступенчатая трансформация

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

    7 июля 2011


    Задача.

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

    Случается, что на XSL падает задача сложной обработки входящего XML, скажем группировки и сортировки. Пример такой задачи рассматривал Дима Филатов в своей статье про алфавитные указатели. Напомню, что там в качестве метода группировки фамилий по первой букве была применена группировка Мюнха, довольно непростой для понимания и чтения метод. Решим ту же самую задачу с помощью многоступенчатой трансформации.

    Итак, на входе у нас есть XML вида:

     <list>
      <item>Орлова</item>
      <item>Владимирова</item>
      <item>Якушева</item>
      <item>Владин</item>
      <item>Александров</item>
      ...
    </list>
    

    Требуется сгруппировать все фамилии по первой букве, отсортировать группы по алфавиту и равномерно распределить группы по n колонкам.


    Шаг первый — сортируем фамилии и сохраняем плоским списком:

     <xsl:variable name="sorted_plain">
      <xsl:for-each select="/list/item">
        <xsl:sort select="." />
        <xsl:copy-of select="." />
      </xsl:for-each>
    </xsl:variable>
    

    Теперь в переменной $sorted_plain хранится временное дерево вида:

     <item>Александров</item>
    <item>Алферова</item>
    <item>Бутыркина</item>
    <item>Владимирова</item>
    <item>Владин</item>
    ...
    

    Шаг второй — группируем фамилии по первой букве:

     <xsl:variable name="sorted_and_grouped">
      <xsl:apply-templates select="exsl:node-set($sorted_plain)/item[1]" mode="items_grouping" />
    </xsl:variable>
      
    <xsl:template match="item" mode="items_grouping">
      <xsl:variable name="first_letter" select="substring(., 1, 1)" />
      <xsl:variable
        name="items_with_this_letter"
        select="../item[substring(., 1, 1) = $first_letter]"
      />
      <group letter="{$first_letter}">
        <xsl:copy-of select="$items_with_this_letter" />
      </group>
      
      <!--
        Входящий список в $sorted_plain отсортирован,
        поэтому в следующую группу отправляем элемент,
        идущий за последним элементом в текущей группе
        -->
      <xsl:apply-templates
        select="$items_with_this_letter[last()]/following-sibling::item[1]"
        mode="items_grouping"
      />
    </xsl:template>
    

    Сейчас в переменной $sorted_and_grouped находится новое временное дерево, содержащее отсортированные группы:

     <group letter="А">
      <item>Александров</item>
      <item>Алферова</item>
    </group>
    <group letter="Б">
      <item>Бутыркина</item>
    </group>
    ...
    

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


    Шаг третий — равномерно распределяем группы по n колонкам-дивам. Это можно сделать, например, так:

     <xsl:variable name="COLS" select="4" />
    <xsl:variable name="groups" select="exsl:node-set($sorted_and_grouped)/group" />
    <xsl:variable
      name="count_of_groups_in_one_col"
      select="ceiling(count($groups) div $COLS)"
    />
      
    <!--
      Импровизированный цикл по счетчику,
      основанный на предположении о том, что число колонок меньше,
      чем количество элементов, распределяемых по этим колонкам
      -->
    <xsl:for-each select="$groups[position() &lt;= $COLS]">
      <xsl:variable name="i" select="position()" />
      
      <div class="col col_{$i}">
        <xsl:for-each select="$groups[
          position() >     $count_of_groups_in_one_col * ($i - 1) and
          position() &lt;= $count_of_groups_in_one_col * $i
        ]">
          <div class="group">
            <h3>
              <xsl:value-of select="@letter" />
            </h3>
            <ul>
              <xsl:for-each select="item">
                <li>
                  <xsl:value-of select="." />
                </li>
              </xsl:for-each>
            </ul>
          </div>
        </xsl:for-each>
      </div>
    </xsl:for-each>
    

    На выходе получаем такой HTML:

     <div class="col col_1">
      <div class="group">
        <h3>А</h3>
        <ul><li>Александров</li><li>Алферова</li></ul>
      </div>
      <div class="group">
        <h3>Б</h3>
        <ul><li>Бутыркина</li></ul>
      </div>
      ...
    </div>
    <div class="col col_2">
      <div class="group">
        <h3>Г</h3>
        <ul><li>Гомиашвили</li><li>Гончар</li><li>Гусев</li></ul>
      </div>
      ...
    </div>
    <div class="col col_3">
      <div class="group">
        <h3>Л</h3>
        <ul><li>Ломов</li></ul>
      </div>
      ...
    </div>
    <div class="col col_4">
      <div class="group">
        <h3>Ф</h3>
        <ul><li>Феоктистов</li><li>Фролов</li></ul>
      </div>
      ...
    </div>
    

    Добавить щепотку CSS — и наш алфавитный указатель готов.


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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9