XSL-T& XSL-FO:重构XML数据以动态创建每个页面的页面序列

时间:2017-03-27 12:56:11

标签: xml xslt xsl-fo apache-fop

我不确定如何用英语描述我的问题,所以我希望我的例子能说清楚我想要做什么。

假设我有以下XML数据:

<ROOT>
  <A>
    <ID>A1</ID>
    <DATA>
      <ENTRY>
        <ENTRYID>Entry1</ENTRYID>
        <ITEM1>Item1</ITEM1>
        <ITEM2>Item2</ITEM2>
        <ITEM3>Item3</ITEM3>
      </ENTRY>
      <ENTRY>
        <ENTRYID>Entry2</ENTRYID>
        <ITEM1>Item2_1</ITEM1>
        <ITEM2>Item2_1</ITEM2>
        <ITEM3>Item2_3</ITEM3>
      </ENTRY>
      ... even more entries...
    </DATA>
  </A>
  <A>
    <ID>A2</ID>
    <DATA>
      <ENTRY>
        <ENTRYID>Entry1</ENTRYID>
        <ITEM1>foo</ITEM1>
        <ITEM2>bar</ITEM2>
        <ITEM3>andsoon</ITEM3>
      </ENTRY>
      <ENTRY>
        <ENTRYID>Entry2</ENTRYID>
        <ITEM1>even</ITEM1>
        <ITEM2>more</ITEM2>
        <ITEM3>items</ITEM3>
      </ENTRY>
      ... even more entries...
    </DATA>
  </A>
  <A>
    .. as many A-Elements as you can think of...
  </A>
</ROOT>

对于我的XML数据中可以有多少A元素或者A-Element中可以有多少个ENTRY元素,没有限制。

所以我有一个现有的XSL-File,它将所有数据放在一个大页面序列(XSL-FO)中。我正在使用Apache FOP来处理XML和XSL。输出格式为PDF。现在,当XML数据非常庞大时,我遇到了内存问题。 在处理大数据时,我已经阅读了很多关于调整性能和内存消耗的内容,并尝试将数据拆分为每页一个页面序列。 我面临的问题是,在我的样式表中处理数据之前,我不知道如何拆分或重组数据。

现在我的样式表匹配A和ENTRY的节点,并将数据格式化为一些设计整齐的表格:

<xsl:template match="A">
  ... print fancy title for table with A/ID ...
  <fo:table>
    <fo:table-header>
       ... fancy table header here ...
    </table-header>
    <fo:table-body>
      <xsl:apply-templates select="DATA/ENTRY"/>
      <fo:table-row>
        ... do some calculating for each A and create a sum table row ...
      </fo:table-row>
    </fo:table-body>
  </fo:table> 
</xsl:template>

<xsl:template match="ENTRY">
  <fo:table-row>
     ... print Entry data in table cells ...
  </fo:table-row>
</xsl:template>

一个A元素的完整表可以延伸数百页(最坏的情况)。我知道有多少Entry-Elements适合一页。由于表头和总和表行,一个A元素的第一页和最后一页将适合较少的ENTRY元素作为其间的页面。 我需要将数据拆分成适当的块。由于我对XML文件的结构没有影响,我需要在样式表中直接执行此操作。

我用xsl:key尝试了一些东西,因为它们在分组数据时工作正常,但我不知道这些是否适用于我的“特殊”分组形式,如果是的话,这将如何工作。

所以我生成的XSL应该是这样的:

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

      <fo:page-sequence master-reference="{$master}">

        <fo:flow flow-name="xsl-region-body" font-size="10pt">

          <xsl:apply-templates select="A"/>
          <xsl:apply-templates select="ENTRY elemnts for first page"/>

        </fo:flow>

      </fo:page-sequence>

      <fo:page-sequence master-reference="{$master}">

        <fo:flow flow-name="xsl-region-body" font-size="10pt">

          <xsl:apply-templates select="ENTRY elemnts for pages in between"/>

        </fo:flow>

      </fo:page-sequence>

      <fo:page-sequence master-reference="{$master}">

        <fo:flow flow-name="xsl-region-body" font-size="10pt">

          <xsl:apply-templates select="ENTRY elemnts for last page"/>

        </fo:flow>

      </fo:page-sequence>

  </fo:root>
</xsl:template>

请注意,中间页面序列必须处于循环中,当然可以有多个A元素。 我不确定如何适当地循环所有页面序列的所有数据。

1 个答案:

答案 0 :(得分:2)

  1. 您可以为每个A开始一个新的fo:page-sequence。每个页面序列只需要几百页,而不是整个文档的几百页的倍数。
  2. 您可以使用递归来选择和处理下一个 n ENTRY元素。
  3. 使用XSLT 2.0可能会产生更清晰,更简洁的代码,但由于您正在讨论使用xsl:key进行分组,看起来您正在使用XSLT 1.0
  4. 不要在循环方面考虑它,在选择和处理源代码中考虑它。毕竟,在XSLT 1.0或XSLT 2.0中没有要更新的循环变量。
  5. 每个A fo:page-sequence一个,A的模板变为:

    <xsl:template match="A">
      <fo:page-sequence master-reference="{$master}">
        <fo:flow flow-name="xsl-region-body" font-size="10pt">
           ... print fancy title for table with A/ID ...
           <fo:table>
             <fo:table-header>
               ... fancy table header here ...
             </table-header>
             <fo:table-body>
               <xsl:apply-templates select="DATA/ENTRY"/>
               <fo:table-row>
                 ... do some calculating for each A and create a sum table row ...
               </fo:table-row>
             </fo:table-body>
           </fo:table>
         </fo:flow>
       </fo:page-sequence>
    </xsl:template>
    

    递归解决方案可能需要处理单页案例以及多页案例:

    <xsl:param name="single-page-count" select="1" />
    <xsl:param name="first-page-count" select="2" />
    <xsl:param name="middle-page-count" select="3" />
    <xsl:param name="last-page-count" select="2" />
    
    <xsl:template match="ROOT">
      <fo:root>
        <fo:layout-master-set>
          <fo:simple-page-master master-name="a">
            <fo:region-body/>
          </fo:simple-page-master>
        </fo:layout-master-set>
        <xsl:apply-templates select="A" />
      </fo:root>
    </xsl:template>
    
    <xsl:template match="A">
      <xsl:variable name="count"
                    select="count(DATA/ENTRY)" />
    
      <xsl:variable name="title">
        <xsl:call-template name="title" />
      </xsl:variable>
    
      <xsl:variable name="sum-row">
        <xsl:call-template name="sum-row" />
      </xsl:variable>
    
      <xsl:choose>
        <xsl:when test="$count &lt;= $single-page-count">
          <xsl:call-template name="page">
            <xsl:with-param name="title" select="$title" />
            <xsl:with-param name="rows">
              <xsl:apply-templates select="DATA/ENTRY"/>
            </xsl:with-param>
            <xsl:with-param name="sum-row" select="$sum-row" />
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="page">
            <xsl:with-param name="title" select="$title" />
            <xsl:with-param name="rows">
              <xsl:apply-templates
                  select="DATA/ENTRY[position() &lt;= $first-page-count]"/>
            </xsl:with-param>
          </xsl:call-template>
          <xsl:call-template name="other-pages">
            <xsl:with-param name="entries"
                            select="DATA/ENTRY[position() > $first-page-count]" />
            <xsl:with-param name="sum-row" select="$sum-row" />
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
    
    <xsl:template name="other-pages">
      <xsl:param name="entries" />
      <xsl:param name="sum-row" />
      <xsl:variable name="count"
                    select="count($entries)" />
    
      <xsl:choose>
        <xsl:when test="$count &lt;= $last-page-count">
          <xsl:call-template name="page">
            <xsl:with-param name="rows">
              <xsl:apply-templates select="$entries"/>
            </xsl:with-param>
            <xsl:with-param name="sum-row" select="$sum-row" />
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="page">
            <xsl:with-param name="rows">
              <xsl:apply-templates
                  select="$entries[position() &lt;= $middle-page-count]"/>
            </xsl:with-param>
          </xsl:call-template>
          <xsl:call-template name="other-pages">
            <xsl:with-param name="entries"
                            select="$entries[position() > $middle-page-count]" />
            <xsl:with-param name="sum-row" select="$sum-row" />
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
    
    <xsl:template name="page">
      <xsl:param name="title" />
      <xsl:param name="rows" />
      <xsl:param name="sum-row" />
    
      <fo:page-sequence master-reference="a">
        <fo:flow flow-name="xsl-region-body">
          <xsl:copy-of select="$title" />
          <fo:table>
            <fo:table-header>
              ... fancy table header here ...
            </fo:table-header>
            <fo:table-body>
              <xsl:copy-of select="$rows" />
              <xsl:copy-of select="$sum-row" />
            </fo:table-body>
          </fo:table>
        </fo:flow>
      </fo:page-sequence>
    </xsl:template>
    
    <xsl:template name="title">
      ... print fancy title for table <xsl:value-of select="ID"/> ...
    </xsl:template>
    
    <xsl:template name="sum-row">
      <fo:table-row>
        ... do some calculating for each A and create a sum table row ...
      </fo:table-row>
    </xsl:template>