Как динамически изменять последовательность страниц xslt на основе атрибутов узла?

Разбавленная версия проблемы, с которой я сталкиваюсь, такова. Для файла XML, например:

<?xml version="1.0" encoding="UTF-8"?>
<items>
    <item cols="1">Item 1</item>
    <item cols="1">Item 2</item>
    <item cols="1">Item 3</item>
    <item cols="1">Item 4</item>
    <item cols="1">Item 5</item>
    <item cols="1">Item 6</item>
    <item cols="1">Item 7</item>
    <item cols="1">Item 8</item>
    <item cols="1">Item 9</item>
    <item cols="2">Item 10</item>
    <item cols="1">Item 11</item>
    <item cols="1">Item 12</item>
    <item cols="1">Item 13</item>
    <item cols="1">Item 14</item>
    <item cols="1">Item 15</item>
    <item cols="1">Item 16</item>
    <item cols="1">Item 17</item>
    <item cols="1">Item 18</item>
</items>

Мне нужно иметь возможность печатать «элементы с cols = 1» в макете страницы с одним столбцом и «элементы с cols = 2» в макете страницы с двумя столбцами. Порядок элементов должен быть сохранен. Все смежные элементы с одинаковым значением @cols должны отображаться как непрерывный поток. Каждый раз, когда значение @cols изменяется, мне нужно перейти на новую страницу и при необходимости изменить макет.

Я делаю что-то вроде этого:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">

    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
            <fo:layout-master-set>

                <fo:simple-page-master master-name="one-column-page-master">
                    <fo:region-body margin-top="3cm" region-name="body" column-count="1"/>
                </fo:simple-page-master>

                <fo:simple-page-master master-name="two-column-page-master">
                    <fo:region-body margin-top="3cm" region-name="body" column-count="2"/>
                    <fo:region-before region-name="header" extent="2cm"/>
                </fo:simple-page-master>

                <fo:page-sequence-master master-name="one-column-page">
                    <fo:repeatable-page-master-reference master-reference="one-column-page-master"/>
                </fo:page-sequence-master>

                <fo:page-sequence-master master-name="two-column-page">
                    <fo:repeatable-page-master-reference master-reference="two-column-page-master"/>
                </fo:page-sequence-master>

            </fo:layout-master-set>

            <xsl:for-each select="//item">
                <xsl:choose>
                    <xsl:when test="@cols = preceding-sibling::item[1]/@cols">
                        <!--cols value hasn't changed, don't create a new page-sequence-->
                        <!--But we cannot directly add fo:flow as the child of fo:root! -->
                        <xsl:call-template name="itemtemplate"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:choose>
                            <xsl:when test="@cols = 1">
                                <fo:page-sequence master-reference="one-column-page">
                                    <xsl:call-template name="itemtemplate"/>
                                </fo:page-sequence>
                            </xsl:when>
                            <xsl:otherwise>
                                <fo:page-sequence master-reference="two-column-page">
                                    <xsl:call-template name="itemtemplate"/>
                                </fo:page-sequence>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
        </fo:root>

    </xsl:template>

    <xsl:template name="itemtemplate">
            <fo:flow flow-name="body">
                <fo:block margin-bottom="5cm">
                    <xsl:apply-templates/>
                </fo:block>
            </fo:flow>

    </xsl:template>

</xsl:stylesheet>

Но, конечно, проблема в том, что я либо должен включать ‹fo:page-sequence..› в свою таблицу стилей, либо нет, я не могу «динамически» решить добавить его на основе атрибутов заметки. (Если только у меня нет метапрограммы, которая в первую очередь создает таблицу стилей динамически, но я надеялся сделать это, используя простые статические таблицы стилей).


person Vineet Bansal    schedule 26.07.2011    source источник
comment
Хороший вопрос. Интересно, решат ли эту проблему функции группировки XSLT 2.0? Пожалуйста, покажите желаемый выходной XML (FO) для вашего образца входных данных, чтобы мы могли лучше понять, к какой цели вы стремитесь.   -  person LarsH    schedule 27.07.2011


Ответы (3)


Вот решение XSLT 2.0, в котором используется xsl:for-each-group с group-adjacent:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fo="http://www.w3.org/1999/XSL/Format">

    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>

    <xsl:template match="/">
      <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
        <fo:layout-master-set>

          <fo:simple-page-master master-name="one-column-page-master">
            <fo:region-body margin-top="3cm" region-name="body" 
                            column-count="1"/>
          </fo:simple-page-master>

          <fo:simple-page-master master-name="two-column-page-master">
            <fo:region-body margin-top="3cm" region-name="body" 
                            column-count="2"/>
            <fo:region-before region-name="header" extent="2cm"/>
          </fo:simple-page-master>

          <fo:page-sequence-master master-name="one-column-page">
            <fo:repeatable-page-master-reference 
                master-reference="one-column-page-master"/>
          </fo:page-sequence-master>

          <fo:page-sequence-master master-name="two-column-page">
            <fo:repeatable-page-master-reference 
                master-reference="two-column-page-master"/>
          </fo:page-sequence-master>

        </fo:layout-master-set>
        <xsl:apply-templates/>
      </fo:root>
    </xsl:template>

    <xsl:template match="items">
      <xsl:for-each-group select="item" 
                          group-adjacent="@cols">

        <xsl:choose>
          <xsl:when test="@cols = 1">
            <fo:page-sequence master-reference="one-column-page">
              <fo:flow flow-name="body">
                <xsl:for-each select="current-group()">
                  <xsl:apply-templates select="."/>
                </xsl:for-each>
              </fo:flow>
            </fo:page-sequence>
          </xsl:when>

          <xsl:otherwise>
            <fo:page-sequence master-reference="two-column-page">
              <fo:flow flow-name="body">
                <xsl:for-each select="current-group()">
                  <xsl:apply-templates select="."/>
                </xsl:for-each>
              </fo:flow>
            </fo:page-sequence>
          </xsl:otherwise>

        </xsl:choose>
      </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="item">
      <fo:block margin-bottom="5cm">
        <xsl:apply-templates/>
      </fo:block>
    </xsl:template>

</xsl:stylesheet>

Выход:

<?xml version="1.0" encoding="UTF-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
   <fo:layout-master-set>
      <fo:simple-page-master master-name="one-column-page-master">
         <fo:region-body margin-top="3cm" region-name="body" column-count="1"/>
      </fo:simple-page-master>
      <fo:simple-page-master master-name="two-column-page-master">
         <fo:region-body margin-top="3cm" region-name="body" column-count="2"/>
         <fo:region-before region-name="header" extent="2cm"/>
      </fo:simple-page-master>
      <fo:page-sequence-master master-name="one-column-page">
         <fo:repeatable-page-master-reference master-reference="one-column-page-master"/>
      </fo:page-sequence-master>
      <fo:page-sequence-master master-name="two-column-page">
         <fo:repeatable-page-master-reference master-reference="two-column-page-master"/>
      </fo:page-sequence-master>
   </fo:layout-master-set>
   <fo:page-sequence master-reference="one-column-page">
      <fo:flow flow-name="body">
         <fo:block margin-bottom="5cm">Item 1</fo:block>
         <fo:block margin-bottom="5cm">Item 2</fo:block>
         <fo:block margin-bottom="5cm">Item 3</fo:block>
         <fo:block margin-bottom="5cm">Item 4</fo:block>
         <fo:block margin-bottom="5cm">Item 5</fo:block>
         <fo:block margin-bottom="5cm">Item 6</fo:block>
         <fo:block margin-bottom="5cm">Item 7</fo:block>
         <fo:block margin-bottom="5cm">Item 8</fo:block>
         <fo:block margin-bottom="5cm">Item 9</fo:block>
      </fo:flow>
   </fo:page-sequence>
   <fo:page-sequence master-reference="two-column-page">
      <fo:flow flow-name="body">
         <fo:block margin-bottom="5cm">Item 10</fo:block>
      </fo:flow>
   </fo:page-sequence>
   <fo:page-sequence master-reference="one-column-page">
      <fo:flow flow-name="body">
         <fo:block margin-bottom="5cm">Item 11</fo:block>
         <fo:block margin-bottom="5cm">Item 12</fo:block>
         <fo:block margin-bottom="5cm">Item 13</fo:block>
         <fo:block margin-bottom="5cm">Item 14</fo:block>
         <fo:block margin-bottom="5cm">Item 15</fo:block>
         <fo:block margin-bottom="5cm">Item 16</fo:block>
         <fo:block margin-bottom="5cm">Item 17</fo:block>
         <fo:block margin-bottom="5cm">Item 18</fo:block>
      </fo:flow>
   </fo:page-sequence>
</fo:root>
person mzjn    schedule 27.07.2011

Мне нужно иметь возможность печатать "элементы, имеющие "cols=1" в макете страницы с одним столбцом, и "элементы, имеющие "cols=2" в макете страницы с двумя столбцами. Порядок элементов должен быть сохранен.

Вы хотите, наконец, сгруппировать соседние элементы item в соответствии со значением @cols в правильной последовательности fo страницы.

Инструкции XSLT 1.0, такие как xsl:choose и xsl:for-each, на самом деле не подходят для этой задачи. Я думаю, вам нужно немного передумать. Вот пример того, как добиться группировки результатов рекурсией.

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


[XSLT 1.0]

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="text()"/>

    <xsl:template match="/*">
        <fo:root>
            <!-- layout master stuff -->
            <xsl:apply-templates select="item"/>
        </fo:root>
    </xsl:template>

    <!-- match @cols 1, first group occurrences -->  
    <xsl:template match="/*/item[@cols=1]
     [not(preceding-sibling::item[1][@cols=1])]">
        <fo:page-sequence master-reference="one-column-page">
            <xsl:copy-of select="."/>
            <xsl:apply-templates select="
                following-sibling::*[1][self::item[@cols=1]]" mode="flow">
                <xsl:with-param name="cols" select="1"/>
            </xsl:apply-templates>
        </fo:page-sequence>
    </xsl:template>

    <!-- match @cols 2, first group occurrences -->
    <xsl:template match="/*/item[@cols=2]
     [not(preceding-sibling::item[1][@cols=2])]">
        <fo:page-sequence master-reference="two-column-page">
            <xsl:copy-of select="."/>
            <xsl:apply-templates select="
                following-sibling::*[1][self::item[@cols=2]]" mode="flow">
                <xsl:with-param name="cols" select="2"/>
            </xsl:apply-templates>
        </fo:page-sequence>
    </xsl:template>

    <!-- recursive match adjacent @cols -->
    <xsl:template match="item" mode="flow">
        <xsl:param name="cols"/>
        <xsl:copy-of select="."/>
        <xsl:apply-templates select="
            following-sibling::*[1][self::item[@cols=$cols]]" mode="flow">
            <xsl:with-param name="cols" select="$cols"/>
        </xsl:apply-templates>
    </xsl:template>

</xsl:stylesheet>

При применении к образцу ввода, указанному в вопросе, получается:

<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
   <fo:page-sequence master-reference="one-column-page">
      <item cols="1">Item 1</item>
      <item cols="1">Item 2</item>
      <item cols="1">Item 3</item>
      <item cols="1">Item 4</item>
      <item cols="1">Item 5</item>
      <item cols="1">Item 6</item>
      <item cols="1">Item 7</item>
      <item cols="1">Item 8</item>
      <item cols="1">Item 9</item>
   </fo:page-sequence>
   <fo:page-sequence master-reference="two-column-page">
      <item cols="2">Item 10</item>
   </fo:page-sequence>
   <fo:page-sequence master-reference="one-column-page">
      <item cols="1">Item 11</item>
      <item cols="1">Item 12</item>
      <item cols="1">Item 13</item>
      <item cols="1">Item 14</item>
      <item cols="1">Item 15</item>
      <item cols="1">Item 16</item>
      <item cols="1">Item 17</item>
      <item cols="1">Item 18</item>
   </fo:page-sequence>
</fo:root>
person Emiliano Poggi    schedule 26.07.2011

@эмпо: Отлично! Таким образом, основной подход заключается в обработке критических элементов (где @cols изменяется) в основном цикле и обработке смежных с ними узлов в рекурсивном вызове шаблона. Я воспользовался вашим подходом и внес несколько изменений, чтобы сделать код проще, но это прекрасно работает!

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="text()"/>

    <xsl:template match="/*">
        <fo:root>

            <xsl:for-each select="item">
                <xsl:choose>
                    <xsl:when test="preceding-sibling::item[1]/@cols != @cols or position()=1">
                        <xsl:choose>
                            <xsl:when test="@cols = 1">
                                <fo:page-sequence master-reference="one-column-page">
                                    <xsl:apply-templates select="." mode="recurse">
                                        <xsl:with-param name="cols" select="@cols"/>
                                    </xsl:apply-templates>
                                </fo:page-sequence>

                            </xsl:when>
                            <xsl:when test="@cols = 2">
                                <fo:page-sequence master-reference="two-column-page">
                                    <xsl:apply-templates select="." mode="recurse">
                                        <xsl:with-param name="cols" select="@cols"/>
                                    </xsl:apply-templates>
                                </fo:page-sequence>
                            </xsl:when>
                        </xsl:choose>
                    </xsl:when>
                </xsl:choose>
            </xsl:for-each>
        </fo:root>
    </xsl:template>


    <!-- recursive match adjacent @cols -->
    <xsl:template match="item" mode="recurse">
        <xsl:param name="cols"/>
        <xsl:copy-of select="."/>
        <xsl:apply-templates select="
            following-sibling::*[1][self::item[@cols=$cols]]"
            mode="recurse">
            <xsl:with-param name="cols" select="$cols"/>
        </xsl:apply-templates>
    </xsl:template>

</xsl:stylesheet>
person Vineet Bansal    schedule 27.07.2011
comment
Спасибо за ваш отзыв. Так почему вы приняли другой ответ? - person Emiliano Poggi; 28.07.2011