Используйте XSLT 1.0 для группировки XML-элементов в сегменты по порядку на основе некоторых критериев.

Скажем, у меня был некоторый XML, который я хотел преобразовать в HTML. XML разделен на упорядоченные разделы:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <section attr="someCriteria">
    <h1>Title 1</h1>
    <p>paragraph 1-1</p>
    <p>paragraph 1-2</p>
  </section>
  <section attr="someOtherCriteria">
    <h3>Subtitle 2</h3>
    <ul>
      <li>list item 2-1</li>
      <li>list item 2-2</li>
      <li>list item 2-3</li>
      <li>list item 2-4</li>
    </ul>
  </section>
  <section attr="anotherSetOfCriteria">
    <warning>
      Warning: This product could kill you
    </warning>
  </section>
  <section attr="evenMoreCriteria">
    <disclaimer>
      You were warned
    </disclaimer>
  </section>
  <section attr="criteriaSupreme">
    <p>Copyright 1999-2011</p>
  </section>
</root>

У меня есть несколько таких XML-документов. Мне нужно сгруппировать и преобразовать эти разделы на основе критериев. Будет два разных типа ведер.

  • Таким образом, первый раздел будет помещен в ведро (например, <div class="FormatOne"></div>).
  • Если второй раздел соответствует критериям для попадания в корзину FormatOne, он также попадет в эту корзину.
  • Если для третьего раздела требуется другое ведро (например, <div class="FormatTwo"></div>), то создается новое ведро, и содержимое раздела помещается в это ведро.
  • Если ведро для четвертого раздела требует «FormatOne» (который отличается от предыдущего формата), то снова создается новое ведро, и содержимое раздела помещается в это ведро.
  • и т. д. Каждый раздел будет помещаться в то же ведро, что и предыдущий раздел, если они имеют одинаковый формат. Если нет, создается новый сегмент.

Таким образом, для каждого документа, в зависимости от логики разделения сегментов, документ может закончиться следующим образом:

<body>
  <div class="FormatOne">
    <h1>Title 1</h1>
    <p>paragraph 1-1</p>
    <p>paragraph 1-2</p>
    <h3>Subtitle 2</h3>
    <ul>
      <li>list item 2-1</li>
      <li>list item 2-2</li>
      <li>list item 2-3</li>
      <li>list item 2-4</li>
    </ul>
  </div>
  <div class="FormatTwo">
    <span class="warningText">
      Warning: This product could kill you
    </span>
  </div>
  <div class="FormatOne">
    <span class="disclaimerText"> You were warned</span>
    <p class="copyright">Copyright 1999-2011</p>
  </div>
</body>

это:

<body>
  <div class="FormatOne">
    <h1>Title 1</h1>
    <p>paragraph 1-1</p>
    <p>paragraph 1-2</p>
    <h3>Subtitle 2</h3>
  </div>
  <div class="FormatTwo">
    <ul>
      <li>list item 2-1</li>
      <li>list item 2-2</li>
      <li>list item 2-3</li>
      <li>list item 2-4</li>
    </ul>
  </div>
  <div class="FormatOne">
    <span class="warningText">
      Warning: This product could kill you
    </span>
    <span class="disclaimerText"> You were warned</span>
    <p class="copyright">Copyright 1999-2011</p>
  </div>
</body>

или даже это:

<body>
  <div class="FormatOne">
    <h1>Title 1</h1>
    <p>paragraph 1-1</p>
    <p>paragraph 1-2</p>
    <h3>Subtitle 2</h3>
    <ul>
      <li>list item 2-1</li>
      <li>list item 2-2</li>
      <li>list item 2-3</li>
      <li>list item 2-4</li>
    </ul>
    <span class="warningText">
      Warning: This product could kill you
    </span>
    <span class="disclaimerText"> You were warned</span>
    <p class="copyright">Copyright 1999-2011</p>
  </div>
</body>

в зависимости от того, как определены разделы.

Есть ли способ использовать XSLT для выполнения такого рода магии группировки?

Любая помощь будет здорово. Спасибо!


person RPNinja    schedule 23.06.2011    source источник


Ответы (3)


Я придумал решение, которое включает в себя последовательное попадание в каждый раздел. Обработка каждого раздела разбита на две части: часть «оболочка» и часть «содержимое». «Оболочка» отвечает за отображение битов <div class="FormatOne">...</div>, а «содержимое» отвечает за отображение фактического содержимого текущего раздела и всех последующих разделов, пока не будет найден несоответствующий раздел.

Когда найден несоответствующий раздел, управление возвращается к шаблону «оболочки» для этого раздела.

Это дает интересную гибкость: шаблоны «оболочки» могут быть очень агрессивными в отношении того, что им соответствует, а разделы «содержимое» могут быть более разборчивыми. В частности, в вашем первом примере вам нужно, чтобы элемент warning отображался как <span class="warningText">...</span>, и это достигается с помощью более точного шаблона.

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

Вы можете увидеть рабочий пример здесь.

Вот мой код, созданный для воспроизведения того, что вы просили в своем первом примере:

<?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" />

    <xsl:template match="/">
        <body>
            <xsl:apply-templates select="/root/section[1]" mode="shell" />
        </body>
    </xsl:template>

    <xsl:template match="section[
        @attr = 'someCriteria' or
        @attr = 'someOtherCriteria' or
        @attr = 'evenMoreCriteria' or
        @attr = 'criteriaSupreme']" mode="shell">

        <div class="FormatOne">
            <xsl:apply-templates select="." mode="contents" />
        </div>

        <xsl:apply-templates select="following-sibling::section[
            @attr != 'someCritera' and
            @attr != 'someOtherCriteria' and
            @attr != 'evenMoreCriteria' and
            @attr != 'criteriaSupreme'][1]" mode="shell" />

    </xsl:template>

    <xsl:template name="nextFormatOne">
        <xsl:variable name="next" select="following-sibling::section[1]" />
        <xsl:if test="$next[
            @attr = 'someCriteria' or
            @attr = 'someOtherCriteria' or
            @attr = 'evenMoreCriteria' or
            @attr = 'criteriaSupreme']">
            <xsl:apply-templates select="$next" mode="contents" />
        </xsl:if>
    </xsl:template>

    <xsl:template match="section[
        @attr = 'someCriteria' or
        @attr = 'someOtherCriteria']" mode="contents">

        <xsl:copy-of select="*" />

        <xsl:call-template name="nextFormatOne" />
    </xsl:template>

    <xsl:template match="section[@attr = 'evenMoreCriteria']" mode="contents">
        <span class="disclaimerText">
            <xsl:value-of select="disclaimer" />
        </span>

        <xsl:call-template name="nextFormatOne" />
    </xsl:template>

    <xsl:template match="section[@attr = 'criteriaSupreme']" mode="contents">
        <p class="copyright">
            <xsl:value-of select="p" />
        </p>

        <xsl:call-template name="nextFormatOne" />
    </xsl:template>

    <xsl:template match="section[@attr = 'anotherSetOfCriteria']" mode="shell">
        <div class="FormatTwo">
            <xsl:apply-templates select="." mode="contents" />
        </div>
        <xsl:apply-templates select="
            following-sibling::section[@attr != 'anotherSetOfCriteria'][1]"
            mode="shell" />
    </xsl:template>

    <xsl:template name="nextFormatTwo">
        <xsl:variable name="next" select="following-sibling::section[1]" />
        <xsl:if test="$next[@attr = 'anotherSetOfCriteria']">
            <xsl:apply-templates select="$next" mode="contents" />
        </xsl:if>
    </xsl:template>

    <xsl:template
        match="section[@attr = 'anotherSetOfCriteria']"
        mode="contents">

        <span class="warningText">
            <xsl:value-of select="warning" />
        </span>

        <xsl:call-template name="nextFormatTwo" />
    </xsl:template>

</xsl:stylesheet>
person Chris Nielsen    schedule 23.06.2011

«Каждый раздел будет помещаться в тот же сегмент, что и предыдущий, если они имеют одинаковый формат. Если нет, создается новый сегмент».

То, что вы описали, по сути является задачей, выполняемой инструкцией

<xsl:for-each-group group-adjacent="....">

в XSLT 2.0. Это предполагает, что вы можете написать функцию, которая переводит ваши "критерии" в имя корзины, и вызывать эту функцию в атрибуте group-adjacent.

Итак, насколько сложно вам использовать XSLT 1.0?

Если вы застряли на версии 1.0, вам придется использовать шаблон проектирования родственной рекурсии, предложенный Крисом Нильсеном.

person Michael Kay    schedule 23.06.2011
comment
Спасибо за это, @Michael! Мы находимся в магазине Microsoft, и, к сожалению, MS не поддерживает XSLT 2.0 (и, вероятно, никогда не будет). Кроме того, мы еще не смогли реализовать что-то вроде Saxon. - person RPNinja; 24.06.2011

Проверьте template, if, choose и for-each:

Они (а также другие элементы XSLT) позволят вам выполнять условное поведение для переключения между различной логикой преобразования.

person Merlyn Morgan-Graham    schedule 23.06.2011