在 xsl:fo 表中按列行值分组

Posted

技术标签:

【中文标题】在 xsl:fo 表中按列行值分组【英文标题】:Grouping by column row values in xsl:fo table 【发布时间】:2020-02-24 21:10:26 【问题描述】:

我正在尝试对 xsl fo:table 中单个列的值进行分组,某些值未按预期分组,它根据前一列的分组值进行分组,

我需要在列中单独分组,检查我的 xsl 和 xml 文件,我正在使用这些文件使用 apache FOP 生成 PDF 文件。

我的 XSL 文件

<?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:fo="http://www.w3.org/1999/XSL/Format"
        exclude-result-prefixes="#all"
        version="3.0">

      <xsl:output method="xml" indent="yes"/>

        <xsl:template match="data">
            <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
                <fo:layout-master-set>
                    <fo:simple-page-master master-name="simple"
                        page- page- margin-top=".5in"
                        margin-bottom=".5in" margin-left=".5in" margin-right=".5in">
                        <fo:region-body margin-top="2cm" margin-bottom="2cm" />
                        <fo:region-before extent="2cm" overflow="hidden" />
                        <fo:region-after extent="1cm" overflow="hidden" />
                    </fo:simple-page-master>
                </fo:layout-master-set>
                <fo:page-sequence master-reference="simple"
                    initial-page-number="1">
                    <fo:static-content flow-name="xsl-region-before">
                        <fo:block font-size="13.0pt" font-family="serif"
                            padding-after="2.0pt" space-before="4.0pt" text-align="center"
                            border-bottom-style="solid" border-bottom->
                            <xsl:text>PDF Test</xsl:text>
                        </fo:block>
                    </fo:static-content>
                    <fo:static-content flow-name="xsl-region-after">
                        <fo:block font-size="12.0pt" font-family="sans-serif"
                            padding-after="2.0pt" space-before="2.0pt" text-align="center"
                            border-top-style="solid" border-bottom->
                            <xsl:text>Page</xsl:text>
                            <fo:page-number />
                        </fo:block>
                    </fo:static-content>
                    <fo:flow flow-name="xsl-region-body">
                        <xsl:apply-templates select="data-body" />
                    </fo:flow>
                </fo:page-sequence>
            </fo:root>
        </xsl:template>
        <xsl:template match="data-body">
            <fo:block text-align="center">
                <fo:table table-layout="fixed" 
                    border-style="dashed">
                    <fo:table-column border-style="solid" />
                    <fo:table-column border-style="solid" />
                    <fo:table-column border-style="solid" />
                    <fo:table-header>
                        <xsl:apply-templates select="table-header" />
                    </fo:table-header>
                    <fo:table-body>
                        <xsl:for-each-group select="table-data" group-adjacent="column-two">
                            <xsl:apply-templates select="current-group()">
                                <xsl:with-param name="row-span" select="count(current-group())" tunnel="yes"/>
                            </xsl:apply-templates>
                        </xsl:for-each-group>
                    </fo:table-body>
                </fo:table>
            </fo:block>
        </xsl:template>

        <xsl:template match="table-header">
            <fo:table-row keep-together.within-page="always"
                border-style="solid">
                <fo:table-cell>
                    <fo:block font-size="10pt" font-family="sans-serif"
                        padding-top="3pt">
                        <xsl:value-of select="column-one"></xsl:value-of>
                    </fo:block>
                </fo:table-cell>
                <fo:table-cell>
                    <fo:block font-size="10pt" font-family="sans-serif"
                        padding-top="3pt">
                        <xsl:value-of select="column-two"></xsl:value-of>
                    </fo:block>
                </fo:table-cell>
                <fo:table-cell>
                    <fo:block font-size="10pt" font-family="sans-serif"
                        padding-top="3pt">
                        <xsl:value-of select="column-three"></xsl:value-of>
                    </fo:block>
                </fo:table-cell>
            </fo:table-row>
        </xsl:template>

        <xsl:template match="table-data">
            <fo:table-row keep-together.within-page="always"
                border-style="solid">
                <xsl:apply-templates>
                    <xsl:with-param name="row-group-index" tunnel="yes" select="position()"/>
                </xsl:apply-templates>
            </fo:table-row>
        </xsl:template>

        <xsl:template match="table-data/*">
            <fo:table-cell>
                <fo:block font-size="10pt" font-family="sans-serif"
                    padding-top="3pt">
                    <xsl:value-of select="."></xsl:value-of>
                </fo:block>
            </fo:table-cell>
        </xsl:template>

        <xsl:template match="table-data/column-two">
            <xsl:param name="row-span" tunnel="yes"/>
            <xsl:param name="row-group-index" tunnel="yes"/>
            <xsl:choose>
                <xsl:when test="$row-span = 1">
                    <xsl:next-match/>
                </xsl:when>
                <xsl:when test="$row-span > 1 and $row-group-index = 1">
                    <fo:table-cell number-rows-spanned="$row-span">
                        <fo:block font-size="10pt" font-family="sans-serif"
                            padding-top="3pt">
                            <xsl:value-of select="."></xsl:value-of>
                        </fo:block>
                    </fo:table-cell>                
                </xsl:when>
            </xsl:choose>
        </xsl:template>

        <xsl:template match="table-data/column-three">
            <xsl:param name="row-span" tunnel="yes"/>
            <xsl:param name="row-group-index" tunnel="yes"/>
            <xsl:choose>
                <xsl:when test="$row-span = 1">
                    <xsl:next-match/>
                </xsl:when>
                <xsl:when test="$row-span > 1 and $row-group-index = 1">
                    <fo:table-cell number-rows-spanned="$row-span">
                        <fo:block font-size="10pt" font-family="sans-serif"
                            padding-top="3pt">
                            <xsl:value-of select="."></xsl:value-of>
                        </fo:block>
                    </fo:table-cell>                
                </xsl:when>
            </xsl:choose>
        </xsl:template>
</xsl:stylesheet>

我的 XML 文件

<?xml version="1.0" encoding="UTF-8"?>
<data>
<data-body>
    <table-header>
        <column-one>Column One</column-one>
        <column-two>Column Two</column-two>
        <column-three>Column Three</column-three>
    </table-header>
    <table-data>
        <column-one>One</column-one>
        <column-two>5000</column-two>
        <column-three>Three</column-three>
    </table-data>
    <table-data>
        <column-one>One</column-one>
        <column-two>5000</column-two>
        <column-three>Three</column-three>
    </table-data>
    <table-data>
        <column-one>One</column-one>
        <column-two>1200</column-two>
        <column-three>Three</column-three>
    </table-data>
    <table-data>
        <column-one>One</column-one>
        <column-two>2000</column-two>
        <column-three>Four</column-three>
    </table-data>
    <table-data>
        <column-one>One</column-one>
        <column-two>2000</column-two>
        <column-three>Four</column-three>
    </table-data>
    <table-data>
        <column-one>One</column-one>
        <column-two>1234</column-two>
        <column-three>Five</column-three>
    </table-data>
    <table-data>
        <column-one>One</column-one>
        <column-two>5666</column-two>
        <column-three>Five</column-three>
    </table-data>
    <table-data>
        <column-one>One</column-one>
        <column-two>5666</column-two>
        <column-three>Five</column-three>
    </table-data>
     </data-body>
</data>

【问题讨论】:

【参考方案1】:

我认为解决此问题的一种方法是使用两次传递,一次计算行跨度并将其添加到列,第二次转换为 XSL-FO。

在https://xsltfiddle.liberty-development.net/3NSSEuY/2 中,我使用XSLT 3 和xsl:iterate 来实现作为xs:string* 参数提供的一系列列名的第一步:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:fo="http://www.w3.org/1999/XSL/Format"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="columns-to-group" as="xs:string*" select="'column-two', 'column-three'"/>

  <xsl:mode name="compute-row-spans" on-no-match="shallow-copy"/>

  <xsl:mode name="compute-row-span" on-no-match="shallow-copy"/>

  <xsl:template match="data-body" mode="compute-row-spans">
      <xsl:iterate select="$columns-to-group">
          <xsl:param name="table" select="."/>
          <xsl:on-completion select="$table"/>
          <xsl:next-iteration>
              <xsl:with-param name="table">
                  <xsl:apply-templates select="$table" mode="compute-row-span">
                      <xsl:with-param name="column-to-group" tunnel="yes" select="."/>
                  </xsl:apply-templates>
              </xsl:with-param>
          </xsl:next-iteration>
      </xsl:iterate>
  </xsl:template>

  <xsl:template match="data-body" mode="compute-row-span">
      <xsl:param name="column-to-group" as="xs:string" tunnel="yes"/>
      <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:apply-templates select="table-header"/>
          <xsl:for-each-group select="table-data" group-adjacent="*[name() = $column-to-group]">
                <xsl:apply-templates select="current-group()" mode="compute-row-span">
                    <xsl:with-param name="column-to-group" tunnel="yes" select="$column-to-group"/>
                    <xsl:with-param name="row-span" select="count(current-group())" tunnel="yes"/>
                </xsl:apply-templates>              
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="table-data" mode="compute-row-span">
      <xsl:copy>
          <xsl:apply-templates select="@*" mode="#current"/>
          <xsl:apply-templates select="*" mode="#current">
              <xsl:with-param name="current-row-index" tunnel="yes" select="position()"/>
          </xsl:apply-templates>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="table-data/*" mode="compute-row-span">
      <xsl:param name="column-to-group" tunnel="yes"/>
      <xsl:param name="row-span" tunnel="yes"/>
      <xsl:param name="current-row-index" tunnel="yes"/>
      <xsl:choose>
          <xsl:when test="name() = $column-to-group and $row-span > 1">
              <xsl:if test="$current-row-index = 1">
                  <xsl:copy>
                      <xsl:apply-templates select="@*" mode="#current"/>
                      <xsl:attribute name="row-span" select="$row-span"/>
                      <xsl:apply-templates mode="#current"/>
                  </xsl:copy>                  
              </xsl:if>
          </xsl:when>
          <xsl:otherwise>
              <xsl:next-match/>
          </xsl:otherwise>
      </xsl:choose>
  </xsl:template>

  <xsl:output method="xml" indent="yes"/>

    <xsl:template match="data">
        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
            <fo:layout-master-set>
                <fo:simple-page-master master-name="simple"
                    page- page- margin-top=".5in"
                    margin-bottom=".5in" margin-left=".5in" margin-right=".5in">
                    <fo:region-body margin-top="2cm" margin-bottom="2cm" />
                    <fo:region-before extent="2cm" overflow="hidden" />
                    <fo:region-after extent="1cm" overflow="hidden" />
                </fo:simple-page-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="simple"
                initial-page-number="1">
                <fo:static-content flow-name="xsl-region-before">
                    <fo:block font-size="13.0pt" font-family="serif"
                        padding-after="2.0pt" space-before="4.0pt" text-align="center"
                        border-bottom-style="solid" border-bottom->
                        <xsl:text>PDF Test</xsl:text>
                    </fo:block>
                </fo:static-content>
                <fo:static-content flow-name="xsl-region-after">
                    <fo:block font-size="12.0pt" font-family="sans-serif"
                        padding-after="2.0pt" space-before="2.0pt" text-align="center"
                        border-top-style="solid" border-bottom->
                        <xsl:text>Page</xsl:text>
                        <fo:page-number />
                    </fo:block>
                </fo:static-content>
                <fo:flow flow-name="xsl-region-body">
                    <xsl:apply-templates select="data-body" />
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>
    <xsl:template match="data-body">
        <fo:block text-align="center">
            <fo:table table-layout="fixed" 
                border-style="dashed">
                <fo:table-column border-style="solid" />
                <fo:table-column border-style="solid" />
                <fo:table-column border-style="solid" />
                <fo:table-header>
                    <xsl:apply-templates select="table-header" />
                </fo:table-header>
                <xsl:variable name="table-with-row-spans">
                    <xsl:apply-templates select="." mode="compute-row-spans"/>
                </xsl:variable>
                <fo:table-body>
                    <xsl:apply-templates select="$table-with-row-spans/data-body/table-data"/>
                </fo:table-body>
            </fo:table>
        </fo:block>
    </xsl:template>

    <xsl:template match="table-header">
        <fo:table-row keep-together.within-page="always"
            border-style="solid">
            <fo:table-cell>
                <fo:block font-size="10pt" font-family="sans-serif"
                    padding-top="3pt">
                    <xsl:value-of select="column-one"></xsl:value-of>
                </fo:block>
            </fo:table-cell>
            <fo:table-cell>
                <fo:block font-size="10pt" font-family="sans-serif"
                    padding-top="3pt">
                    <xsl:value-of select="column-two"></xsl:value-of>
                </fo:block>
            </fo:table-cell>
            <fo:table-cell>
                <fo:block font-size="10pt" font-family="sans-serif"
                    padding-top="3pt">
                    <xsl:value-of select="column-three"></xsl:value-of>
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </xsl:template>

    <xsl:template match="table-data">
        <fo:table-row keep-together.within-page="always"
            border-style="solid">
            <xsl:apply-templates/>
        </fo:table-row>
    </xsl:template>

    <xsl:template match="table-data/*">
        <fo:table-cell>
            <fo:block font-size="10pt" font-family="sans-serif"
                padding-top="3pt">
                <xsl:apply-templates/>
            </fo:block>
        </fo:table-cell>        
    </xsl:template>

    <xsl:template match="table-data/*[@row-span]">
        <fo:table-cell number-rows-spanned="@row-span">
            <fo:block font-size="10pt" font-family="sans-serif"
                padding-top="3pt">
                <xsl:apply-templates/>
            </fo:block>
        </fo:table-cell>                
    </xsl:template>

</xsl:stylesheet>

【讨论】:

嗨,马丁,它很棒并且完全按预期工作..感谢您的回答,这对我很有帮助,您是 XSLMAN!? 嗨马丁,通过按照这种方式对值进行分组,我如何将特定样式应用于列单元格?例如单元格对齐,边框属性??..请澄清

以上是关于在 xsl:fo 表中按列行值分组的主要内容,如果未能解决你的问题,请参考以下文章

将预定义的数字分配给数据框中的列行值

SQL Server 动态行转列(参数化表名分组列行转列字段字段值)

SQL Server 动态行转列(参数化表名分组列行转列字段字段值)

SQL Server 动态行转列(参数化表名分组列行转列字段字段值)

熊猫数据框分组和求和,组内,跨行值而不是按列

使用 AG 网格时是不是可以只显示列行?