使用XSLT将XML转换为将字段转换为列

时间:2016-01-20 11:19:36

标签: xml xslt xsl-fo

我目前有一个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>

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

我想使用XSLT和XSL-FO对其进行转换和格式化,以生成包含四列的PDF,每行列出字段名称,然后列出三个Person对象的字段值。例如,由于上面列出了四个人,我希望表格如下所示:

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)。我不想这样做,因为从长远来看,我希望能够在不修改软件的情况下发送不同的XSL模板文件,但如果以前的那些不可能,我可以回到这个选项

2 个答案:

答案 0 :(得分:1)

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

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>

呈现为:

enter image description here

答案 1 :(得分:1)

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>