使用 XSLT 转换 XML 以将字段分组到列中

Transforming XML to group fields into columns with XSLT

我目前有一个 XML 文件,如下所示:

<Plan>
  <People>
    <Person>
      <name>Fred Bloggs</name>
      <position>CEO</position>
      <responsibility>Everything</responsibility>
    </Person>
    <Person>
      <name>Joe Bloggs</name>
      <position>Cleaner</position>
      <responsibility>Cleaning</responsibility>
    </Person>
    <Person>
      <name>Wilma Bloggs</name>
      <position>CTO</position>
      <responsibility>Tech stuff</responsibility>
    </Person>
    <Person>
      <name>Betty Bloggs</name>
      <position>MD</position>
      <responsibility>Management</responsibility>
    </Person>
  </People>
</Plan>

实际上它是一个人员列表,每个人都有一个字段列表。字段列表以后会扩展,但是每个人都会有相同的字段。

我想使用 XSLT 和 XSL-FO 对其进行转换和格式化,以生成具有四列的 PDF,每行列出字段名称,然后列出三个 Person 对象的字段值。例如,上面列出了四个人,我希望 table 看起来像这样:

Name            Fred Bloggs   Joe Bloggs  Wilma Bloggs
Position        CEO           Cleaner     CTO
Responsibility  Everything    Cleaning    Tech stuff
Name            Betty Bloggs
Position        MD
Responsibility  Management

实际上,在 XML 文件中,我按人员对字段进行分组(因此每个人都有三个字段),而在最终输出中,我想按字段对人员进行分组(因此每一行具有相同的三个不同的人的字段)。

如果我手动编写 XSL-FO 代码,我可以通过为每一行沿着这些行生成标记来实现此目的:

<fo:table-row>
  <fo:table-cell>
    <fo:block>Name</fo:block>
  </fo:table-cell>
  <fo:table-cell>
    <fo:block>Fred Bloggs</fo:block>
  </fo:table-cell>
  <fo:table-cell>
    <fo:block>Joe Bloggs</fo:block>
  </fo:table-cell>
  <fo:table-cell>
    <fo:block>Wilma Bloggs</fo:block>
  </fo:table-cell>
</fo:table-row>

这给了我想要的结果,所以我知道 XSL-FO 到 PDF 部分工作正常,但我需要自动化该过程。我可以直接通过 XSLT 实现吗?

我能想到的另一个选择是生成一个单独的 XML 文件,该文件明确匹配布局结构,然后对其进行转换,例如:

<PersonTable>
  <PersonRow>
    <name1>Fred Bloggs</name1>
    <name2>Joe Bloggs</name2>
    <name3>Wilma Bloggs</name3>
  </PersonRow>
</PersonTable>

最后的、最不受欢迎的选择是自动生成完整的 XSL-FO 数据,完全避开转换步骤(即我有效地从 XSL-FO 转换为 PDF)。我不想像在 long 运行 中那样做,我希望能够在不修改软件的情况下发布不同的 XSL 模板文件,但是如果之前的选项不可行,我可以退回到这个选项.

为了简化问题(对我而言),以下样式表以请求的格式创建了一个简单的 HTML table:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:param name="cols" select="3" />

<xsl:template match="People">
    <table border="1">
        <xsl:apply-templates select="Person[position() mod $cols = 1]"/>
    </table>
</xsl:template>

<xsl:template match="Person">
    <xsl:variable name="group" select=". | following-sibling::Person[position() &lt; $cols]" />
    <xsl:for-each select="*">
        <xsl:variable name="i" select="position()" />
        <tr>
            <td>
                <xsl:value-of select="name()"/>
            </td>
            <xsl:for-each select="$group">
                <td>
                    <xsl:value-of select="*[$i]"/>
                </td>
            </xsl:for-each>
        </tr>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

应用于你的例子,结果是:

<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
   <tr>
      <td>name</td>
      <td>Fred Bloggs</td>
      <td>Joe Bloggs</td>
      <td>Wilma Bloggs</td>
   </tr>
   <tr>
      <td>position</td>
      <td>CEO</td>
      <td>Cleaner</td>
      <td>CTO</td>
   </tr>
   <tr>
      <td>responsibility</td>
      <td>Everything</td>
      <td>Cleaning</td>
      <td>Tech stuff</td>
   </tr>
   <tr>
      <td>name</td>
      <td>Betty Bloggs</td>
   </tr>
   <tr>
      <td>position</td>
      <td>MD</td>
   </tr>
   <tr>
      <td>responsibility</td>
      <td>Management</td>
   </tr>
</table>

呈现为:

调整我的 two adjacent tables in body-region each with two columns(xsl-fo) 解决方案并仍在使用 XSLT 1.0:

  <xsl:param name="cols" select="3" />

  <xsl:template match="People">
    <fo:table>
      <fo:table-body>
        <xsl:call-template name="rows" />
      </fo:table-body>
    </fo:table>
  </xsl:template>

  <xsl:template name="rows">
    <xsl:param name="persons" select="*" />

    <xsl:call-template name="row-group">
      <xsl:with-param name="persons"
                      select="$persons[position() &lt;= $cols]" />
    </xsl:call-template>
    <xsl:if test="count($persons) > $cols">
      <xsl:call-template name="rows">
        <xsl:with-param name="persons" select="$persons[position() > $cols]" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template name="row-group">
    <xsl:param name="persons" />

    <xsl:for-each select="$persons[1]/*">
      <xsl:variable name="position" select="position()" />
      <fo:table-row>
        <fo:table-cell>
          <fo:block><xsl:value-of select="local-name()" /></fo:block>
        </fo:table-cell>
        <xsl:for-each select="$persons">
          <fo:table-cell>
            <fo:block><xsl:apply-templates select="./*[position()= $position]" /></fo:block>
          </fo:table-cell>
        </xsl:for-each>
      </fo:table-row>
    </xsl:for-each>
  </xsl:template>