这是您正在寻找的Muenchian分组解决方案。
从您提供的原始XML开始,我认为按AreaID分组就足够了,但事实证明,还需要由UnitID进行第二次分组。
这是我修改后的XSLT 1.0解决方案。这是不是有很多比原来的解决方案更为复杂:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:key name="kPlanByArea" match="Plan"
use="@AreaID" />
<xsl:key name="kPlanByAreaAndUnit" match="Plan"
use="concat(@AreaID, ',', @UnitID)" />
<xsl:template match="/">
<xsl:apply-templates select="Root/Plans" />
</xsl:template>
<!-- main template -->
<xsl:template match="Plans">
<ol>
<!-- group by '{@AreaID}' (note the template mode!) -->
<xsl:apply-templates mode="area-group" select="
Plan[
generate-id()
=
generate-id(
key('kPlanByArea', @AreaID)[1]
)
]
">
<xsl:sort select="@AreaID" data-type="number" />
</xsl:apply-templates>
</ol>
</xsl:template>
<!-- template to output each '{@AreaID}' group -->
<xsl:template match="Plan" mode="area-group">
<li>
<xsl:value-of select="concat('Area ', @AreaID)" />
<ol>
<!-- group by '{@AreaID},{@UnitID}' -->
<xsl:apply-templates mode="unit-group" select="
key('kPlanByArea', @AreaID)[
generate-id()
=
generate-id(
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1]
)
]
">
<xsl:sort select="@UnitID" data-type="number" />
</xsl:apply-templates>
</ol>
</li>
</xsl:template>
<!-- template to output each '{@AreaID},{@UnitID}' group -->
<xsl:template match="Plan" mode="unit-group">
<li>
<xsl:value-of select="concat('Unit ', @UnitID)" />
<ol>
<xsl:apply-templates select="
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))/Part
">
<xsl:sort select="@UnitID" data-type="number" />
</xsl:apply-templates>
</ol>
</li>
</xsl:template>
<!-- template to output Parts into a list -->
<xsl:template match="Part">
<li>
<xsl:value-of select="concat('Part ', @ID, ' (', @Name ,')')" />
</li>
</xsl:template>
</xsl:stylesheet>
由于您的XML缺少它,我添加了一个单元ID到组:
<Plan AreaID="1" UnitID="86">
<Part ID="8651" Name="zzz" />
</Plan>
这里是输出:
<ol>
<li>Area 1
<ol>
<li>Unit 83
<ol>
<li>Part 9122 (foo)</li>
<li>Part 9126 (bar)</li>
</ol>
</li>
<li>Unit 86
<ol>
<li>Part 8650 (baz)</li>
<li>Part 8651 (zzz)</li>
</ol>
</li>
<li>Unit 95
<ol>
<li>Part 7350 (meh)</li>
</ol>
</li>
</ol>
</li>
<li>Area 2
<ol>
<li>Unit 26
<ol>
<li>Part 215 (quux)</li>
</ol>
</li>
</ol>
</li>
</ol>
由于您似乎很难用XSL键,在这里我尝试了一个解释:
<xsl:key>
与许多编程语言已知的关联数组(map,hash,无论您称之为什么)完全等价。这:
<xsl:key name="kPlanByAreaAndUnit" match="Plan"
use="concat(@AreaID, ',', @UnitID)" />
产生,可以在JavaScript中这样来表达一个数据结构:
var kPlanByAreaAndUnit = {
"1,83": ['array of all <Plan> nodes with @AreaID="1" and @UnitID="83"'],
"1,86": ['array of all <Plan> nodes with @AreaID="1" and @UnitID="86"'],
/* ... */
"1,95": ['array of all <Plan> nodes with @AreaID="1" and @UnitID="95"']
};
访问数据结构中的函数被调用key()
。所以,这XPath表达式:
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))
是(在JavaScript,再次)的逻辑等效的:匹配给定的所有节点的
kPlanByAreaAndUnit[this.AreaID + ',' + this.UnitID];
返回一个数组(一个节点集合中,更正确地)密钥字符串(密钥始终是一个字符串)。该节点集可以像XSLT中的任何其他节点集一样使用,即与通过“传统”XPath检索的节点集相似。这意味着你可以申请条件(谓语),给它:
<!-- first node only... -->
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1]
<!-- nodes that have <Part> children only... -->
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[Part]
或将其用作导航的XPath基地:
<!-- the actual <Part> children of matched nodes... -->
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))/Part
等。这也意味着我们可以将它用作<xsl:apply-templates>
的“选择”表达式,我们可以将其用作分组的基础。这使我们对上述样式表的核心(如果你已经缠这一个你的头,你已经理解了解决方案的其他部分一样):
key('kPlanByArea', @AreaID)[
generate-id()
=
generate-id(
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1]
)
]
在JavaScript中再次,这可以表示为:
// the result will be a node-set, so we prepare an array
var selectedNodes = [];
// "key('kPlanByArea', @AreaID)"
var nodeSet = kPlanByArea[this.AreaID];
// "[...]" - the [] actually triggers a loop that applies
// the predicate expression to all nodes in the set, so we do:
for (var i = 0; i < nodeSet.length; i++) {
// use the current node for any calculations
var c = nodeSet[i];
if (
// if the current node === the *first* node in kPlanByAreaAndUnit...
generateId(c)
==
generateId(kPlanByAreaAndUnit[c.AreaID + ',' + c.UnitID][0])
) {
// ...include it in the resulting selection
selectedNodes.push(c)
}
}
表达完成后,只有那些节点被选择的是与给定的“areaID表示,单元ID”组合各自的第一的 - 我们实际上已经编组他们在他们的“areaID表示,单元ID”的组合。
将模板应用于此节点集会导致每个组合只出现一次。然后,我的<xsl:template match="Plan" mode="unit-group">
再次检索完整列表以实现每个组的完整输出。
我希望使用JavaScript来解释这个概念是一个有用的想法。
谢谢,这让我更加接近。我还是有一点麻烦 - 第二级元素是重复的,而不是分组,所以我得到 区1 单元83 部分9122 单元83 部分9126 单位86 部分8650 代替 区1个 单元83 部分9122 部分9126 单元86 部分8650 但它更接近了很多比我! – Val 2009-06-04 17:58:13
哎呀,我看到的评论没有格式相同的方式作为帖子:( – Val 2009-06-04 18:00:06
不,评论格式为纯文本(但尖括号<>是允许的)。请检查我修改后的解决方案。:) – Tomalak 2009-06-09 10:24:41