使用 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() < $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() <= $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>
我目前有一个 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() < $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() <= $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>