使用 XSLT 在 3 列中输出分组列表
Posted
技术标签:
【中文标题】使用 XSLT 在 3 列中输出分组列表【英文标题】:Output grouped list in 3 columns using XSLT 【发布时间】:2012-07-29 09:14:09 【问题描述】:我想在 3 列中输出分组结果。我没有使用表格,所以我想我的意思是并排显示 3 个结果部分(参见 html)。
我根据其中一个节点的值对结果进行了分组,并在一个标题下分组,该标题是节点的值。例如:
<All_Results>
<Result>
<Dept>Finance</Dept>
<Name>Bob</Name>
</Result>
<Result>
<Dept>Finance</Dept>
<Name>Susan</Name>
</Result>
<Result>
<Dept>Sales</Dept>
<Name>Igor</Name>
</Result>
</All_Results>
格式如下:
<li>
<h4>Finance</h4>
<ul>
<li>Bob</li>
</ul>
<ul>
<li>Susan</li>
</ul>
<h4>Sales</h4>
<ul>
<li>Igor</li>
</ul>
</li>
这很有效,我很满意。现在我要做的是并排创建 3 列 Depts 及其结果(即本例中的名称)。我预计有 9 个可能的部门,但这可能会在未来发生变化。
到目前为止,这是我的 XSLT(到目前为止,它所做的只是上面的格式,还没有在列上工作,我不知道如何解决这个问题):
<xsl:output method="html" />
<xsl:key name="results-by-dept" match="Result" use="Dept" />
<xsl:template match="All_Results">
<xsl:variable name="Rows" select="Result" />
<xsl:variable name="RowCount" select="count($Rows)" />
<ul class="deptList">
<xsl:for-each select="Result[count(. | key('results-by-dept', Dept)[1]) = 1]">
<xsl:sort select="Dept" order ="ascending"/>
<li>
<h4>
<xsl:value-of select="Dept" />
</h4>
<ul>
<xsl:for-each select="key('results-by-dept', Dept)">
<xsl:sort select="Name" />
<li>
<xsl:value-of select="Name"/>
</li>
</xsl:for-each>
</ul>
</li>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>
任何帮助将不胜感激!
肖恩的编辑
在与 OP 讨论后,我认为这个用例说明了他想要什么。以下示例输入文档显示了 5 个部门,有 1 名或 2 名员工。节点的顺序并不重要。
用例 1:输入文档
<All_Results>
<Result>
<Dept>Finance</Dept>
<Name>Bob</Name>
</Result>
<Result>
<Dept>Engineering</Dept>
<Name>Inna</Name>
</Result>
<Result>
<Dept>Finance</Dept>
<Name>Susan</Name>
</Result>
<Result>
<Dept>Sales</Dept>
<Name>Igor</Name>
</Result>
<Result>
<Dept>Human resources</Dept>
<Name>Jane</Name>
</Result>
<Result>
<Dept>Admin</Dept>
<Name>Joe</Name>
</Result>
<Result>
<Dept>Engineering</Dept>
<Name>Dima</Name>
</Result>
<Result>
<Dept>Human resources</Dept>
<Name>Beth</Name>
</Result>
</All_Results>
输出将像这样进行转换(如下列表)。 HTML 表格将由 3 列但只有 1 行构成。每个单元格都包含一个以部门名称为首的部门员工的 HTML 无序列表。每列包含大约三分之一的部门,最后一列在这方面参差不齐。从上到下然后从左到右阅读,部门应该按字母顺序排序。在每个部门内,员工应按字母顺序列出。
用例 1:输出文档
<table>
<tr>
<td>
<ul class="deptList">
<li>
<h4>Admin</h4>
<ul>
<li>Joe</li>
</ul>
</li>
<li>
<h4>Engineering</h4>
<ul>
<li>Dima</li>
<li>Inna</li>
</ul>
</li>
</ul>
</td>
<td>
<ul class="deptList">
<li>
<h4>Finance</h4>
<ul>
<li>Bob</li>
<li>Susan</li>
</ul>
</li>
<li>
<h4>Human resources</h4>
<ul>
<li>Beth</li>
<li>Jane</li>
</ul>
</li>
</ul>
</td>
<td>
<ul class="deptList">
<li>
<h4>Sales</h4>
<ul>
<li>Igor</li>
</ul>
</li>
</ul>
</td>
</tr>
</table>
【问题讨论】:
请用 3 列显示您的预期输出。你的意思是什么类型的列?也许您的意思是一个包含 3 列的 HTML 表格? 嗨,肖恩。我刚刚编辑了顶部的原始帖子。我的意思不是在 HTML 表格中,所以“列”具有误导性。我可以在没有桌子的情况下这样做吗? 什么是“结果部分”? i.imgur.com/1sgpm.png 我想得到这样的结果 所以你说的不是HTML表格,然后你显示一个表格? 【参考方案1】:这个 XSLT 1.0 转换:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kDeptByVal" match="Dept" use="."/>
<xsl:key name="kEmpByDept" match="Name" use="../Dept"/>
<xsl:variable name="vDeptsUniq" select=
"/*/*/Dept[generate-id() = generate-id(key('kDeptByVal',.)[1])]"/>
<xsl:variable name="vDeptsPerCol" select="ceiling(count($vDeptsUniq) div 3)"/>
<xsl:template match="/*">
<table border="1">
<tr>
<xsl:for-each select="$vDeptsUniq[not(position() > 3)]">
<xsl:variable name="vCol" select="position()"/>
<td>
<ul class="deptList">
<xsl:for-each select="$vDeptsUniq">
<xsl:sort/>
<xsl:if test=
"position() > ($vCol -1)*$vDeptsPerCol
and
not(position() > $vCol*$vDeptsPerCol)
">
<li>
<h4><xsl:value-of select="."/></h4>
<ul>
<xsl:apply-templates select="key('kEmpByDept', .)">
<xsl:sort/>
</xsl:apply-templates>
</ul>
</li>
</xsl:if>
</xsl:for-each>
</ul>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
<xsl:template match="Name">
<li><xsl:value-of select="."/></li>
</xsl:template>
</xsl:stylesheet>
应用于提供的 XML 文档时:
<All_Results>
<Result>
<Dept>Finance</Dept>
<Name>Bob</Name>
</Result>
<Result>
<Dept>Engineering</Dept>
<Name>Inna</Name>
</Result>
<Result>
<Dept>Finance</Dept>
<Name>Susan</Name>
</Result>
<Result>
<Dept>Sales</Dept>
<Name>Igor</Name>
</Result>
<Result>
<Dept>Human resources</Dept>
<Name>Jane</Name>
</Result>
<Result>
<Dept>Admin</Dept>
<Name>Joe</Name>
</Result>
<Result>
<Dept>Engineering</Dept>
<Name>Dima</Name>
</Result>
<Result>
<Dept>Human resources</Dept>
<Name>Beth</Name>
</Result>
</All_Results>
产生想要的正确结果:
<table border="1">
<tr>
<td>
<ul class="deptList">
<li>
<h4>Admin</h4>
<ul>
<li>Joe</li>
</ul>
</li>
<li>
<h4>Engineering</h4>
<ul>
<li>Dima</li>
<li>Inna</li>
</ul>
</li>
</ul>
</td>
<td>
<ul class="deptList">
<li>
<h4>Finance</h4>
<ul>
<li>Bob</li>
<li>Susan</li>
</ul>
</li>
<li>
<h4>Human resources</h4>
<ul>
<li>Beth</li>
<li>Jane</li>
</ul>
</li>
</ul>
</td>
<td>
<ul class="deptList">
<li>
<h4>Sales</h4>
<ul>
<li>Igor</li>
</ul>
</li>
</ul>
</td>
</tr>
</table>
【讨论】:
Dimitre,感谢您提交此内容。我从不同的解决方案中学到了很多东西。【参考方案2】:这个 XSLT 1.0 样式表将为您提供 3 列...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kDepartments" match="Result" use="Dept" />
<xsl:template match="/">
<table>
<tr>
<xsl:variable name="dept-count" select="count( */Result[
generate-id(.) = generate-id( key('kDepartments',Dept)[1])])" />
<xsl:for-each select="(//*)[position() <= 3]">
<xsl:variable name="column" select="position()" />
<td>
<ul class="deptList">
<xsl:for-each select="/*/Result[
generate-id(.) = generate-id( key('kDepartments',Dept)[1])]" >
<xsl:sort select="Dept" />
<xsl:variable name="DeptIndex" select="position()" />
<xsl:apply-templates select="self::Result[
(floor((($DeptIndex - 1)*3 div $dept-count)) + 1) = $column]" />
</xsl:for-each>
</ul>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
<xsl:template match="Result">
<h4>
<xsl:value-of select="Dept" />
</h4>
<ul>
<xsl:for-each select="key('kDepartments', Dept)">
<xsl:sort select="Name" />
<li>
<xsl:value-of select="Name"/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>
【讨论】:
【参考方案3】:这是 XSLT 1.0 中的一种方法:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="depts" match="Dept" use="." />
<xsl:key name="results-by-dept" match="Result" use="Dept" />
<xsl:template match="All_Results">
<xsl:variable name="max-cols" select="3"/>
<xsl:variable name="depts"
select="Result/Dept[count(.|key('depts', .)[1]) = 1]"/>
<xsl:variable name="col-size"
select="ceiling(count($depts) div $max-cols)"/>
<table>
<tr>
<xsl:for-each select="$depts[(position() - 1) mod $col-size = 0]">
<xsl:variable name="col" select="position() - 1"/>
<td>
<ul class="deptList">
<xsl:for-each select="$depts">
<xsl:sort select="."/>
<xsl:if test="floor((position() - 1) div $col-size) = $col">
<li>
<xsl:apply-templates select="."/>
</li>
</xsl:if>
</xsl:for-each>
</ul>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
<xsl:template match="Dept">
<h4>
<xsl:value-of select="." />
</h4>
<ul>
<xsl:for-each select="key('results-by-dept', .)">
<xsl:sort select="Name" />
<li>
<xsl:value-of select="Name"/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>
一些注意事项:
这里的主要技巧是使用$depts[(position() - 1) mod $col-size = 0]
循环尽可能多的列。
循环遍历列的另一种方法是使用递归模板并递增计数器变量。
这在 XSLT 2.0 中会容易得多。
Muenchian grouping 中的count(.|key('depts', .)[1]) = 1
谓词可以等效地写为generate-id() = generate-id(key('depts', .)[1])
。我更喜欢后者,因为我认为它不那么晦涩难懂,但我在这里使用了前者,因为它是问题中使用的。
【讨论】:
谢谢尤卡。你的笔记很有用!以上是关于使用 XSLT 在 3 列中输出分组列表的主要内容,如果未能解决你的问题,请参考以下文章
在 DotNetNuke 表单和列表中使用 XSLT 对项目进行分组
使用 XSLT 对 HTML 输出进行分组(muenchian 分组?)