好的,我知道已经提出并回答过这方面的变化;我整天都在读它们,但我仍然被卡住了。所以,这里是:
我需要使用某些XML在HTML中创建摘要列表。
鉴于此XML:
<Root><!-- yes, I know I don't need a 'Root' element! Legacy code... -->
<Plans>
<Plan AreaID="1" UnitID="83">
<Part ID="9122" Name="foo" />
<Part ID="9126" Name="bar" />
</Plan>
<Plan AreaID="1" UnitID="86">
<Part ID="8650" Name="baz" />
</Plan>
<Plan AreaID="2" UnitID="26">
<Part ID="215" Name="quux" />
</Plan>
<Plan AreaID="1" UnitID="95">
<Part ID="7350" Name="meh" />
</Plan>
</Plans>
</Root>
我需要发出:
<ol>
<li>Area 1:
<ol><!-- units in Area 1 -->
<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>
</ol>
<li>Unit 95:
<ol>
<li>Part 7350 (meh)</li>
</ol>
</li>
</ol><!-- /units in Area 1-->
</li>
<li>Area 2:
<ol><!-- units in Area 2 -->
<li>Unit 26:
<ol>
<li>Part 215 (quux)</li>
</ol>
</li>
</ol><!-- /units in Area 2-->
</li>
</ol>
我有外部分组工作 - 我获得了区域1和2的顶级列表元素。但我无法得到区域中的单位序列 - 我要么没有输出,要么重复相同的值。我甚至没有达到Part级别: - (
我一直在研究这样的样式表:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
<xsl:output method="html" indent="yes"/>
<xsl:key name="kAreaID" match="Plan" use="@AreaID" />
<xsl:key name="kUnitID" match="Plan" use="@UnitID" />
<xsl:template match="/Root/Plans">
<html><head><title>test grouping</title></head>
<body>
<ol>
<xsl:for-each select="./Plan[generate-id(.) =
generate-id( key( 'kAreaID', @AreaID )[1] )]"
>
<xsl:sort order="ascending" select="./@AreaID" />
<li>Area <xsl:value-of select="@AreaID"/>:
<ol>
<xsl:for-each select="key( 'kUnitID', @UnitID )">
<li>Unit <xsl:value-of select="@UnitID"/>:
<ol>
<li>(Parts go here...)</li>
</ol>
</li>
</xsl:for-each>
</ol>
</li>
</xsl:for-each>
</ol>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
非常感谢任何帮助!
答案 0 :(得分:18)
以下是您正在寻找的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,我添加了一个UnitID来分组:
<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,UnitID”组合的第一个节点 - 实际上我们将它们分组在它们的“AreaID,UnitID”组合中。
将模板应用于此节点集会导致每个组合仅出现一次。我的<xsl:template match="Plan" mode="unit-group">
然后再次检索完整列表以实现每个组的完整输出。
我希望使用JavaScript来解释这个概念是一个有用的想法。
答案 1 :(得分:1)
我认为您根本不需要使用 kUnitID 键。而是替换以下行...
<xsl:for-each select="key( 'kUnitID', @UnitID )">
..而是使用此行,它应该遍历与当前AreaID匹配的所有部分
<xsl:for-each select="key( 'kAreaID', @AreaID )">
在这个循环中,对于你的(零件转到这里......)代码,你可以简单地循环部分
<xsl:for-each select="Part">
<li>Part (<xsl:value-of select="@ID" />)</li>
</xsl:for-each>
答案 2 :(得分:1)
好吧,我暂时放弃了钥匙和Muenchian分组。我几乎不理解它,并且抨击它并没有产生预期的结果。我理解递归,所以我采用这种递归方法产生了所需的输出。我找到了 http://www.biglist.com/lists/xsl-list/archives/200412/msg00865.html
讨论主题警告说,与Muenchian方法相比,性能会受到大量输入的影响,下面的解决方案是冗长和重复的(我可以对其进行重构以使其更小更难理解;-)但是1)它实际上<对我来说,强>工作,2)对于我目前的问题,输入集非常小,不超过十几个底层的Part节点。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- recursive grouping http://www.biglist.com/lists/xsl-list/archives/200412/msg00865.html -->
<xsl:template match="//Plans">
<html>
<head>
<title>test grouping</title>
</head>
<body>
<ol>
<xsl:call-template name="PlanGrouping">
<xsl:with-param name="list" select="Plan"/>
</xsl:call-template>
</ol>
</body>
</html>
</xsl:template>
<xsl:template name="PlanGrouping">
<xsl:param name="list"/>
<!-- Selecting the first Area ID as group identifier and the group itself-->
<xsl:variable name="group-identifier" select="$list[1]/@AreaID"/>
<xsl:variable name="group" select="$list[@AreaID = $group-identifier]"/>
<!-- Do some work for the group -->
<li>
Area <xsl:value-of select="$group-identifier"/>:
<ol>
<xsl:call-template name="AreaGrouping">
<xsl:with-param name="list" select="$list[(@AreaID = $group-identifier)]"/>
</xsl:call-template>
</ol>
</li>
<!-- If there are other groups left, calls itself -->
<xsl:if test="count($list)>count($group)">
<xsl:call-template name="PlanGrouping">
<xsl:with-param name="list" select="$list[not(@AreaID = $group-identifier)]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="AreaGrouping">
<xsl:param name="list"/>
<!-- Selecting the first Unit ID as group identifier and the group itself-->
<xsl:variable name="group-identifier" select="$list[1]/@UnitID"/>
<xsl:variable name="group" select="$list[@UnitID = $group-identifier]"/>
<!-- Do some work for the group -->
<li>
Unit <xsl:value-of select="$group-identifier"/>:
<ol>
<xsl:call-template name="Parts">
<xsl:with-param name="list" select="$list[(@UnitID = $group-identifier)]"/>
</xsl:call-template>
</ol>
</li>
<!-- If there are other groups left, calls itself -->
<xsl:if test="count($list)>count($group)">
<xsl:call-template name="AreaGrouping">
<xsl:with-param name="list" select="$list[not(@UnitID = $group-identifier)]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="Parts">
<xsl:param name="list"/>
<xsl:for-each select="$list/Part">
<li>
Part <xsl:value-of select="@ID"/> (<xsl:value-of select="@Name"/>)
</li>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
答案 3 :(得分:0)
这可以做你想要的但是递归,而不是分组。对不起,我还在学习如何使用分组:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:key name="kAreaID" match="Plan" use="@AreaID" />
<xsl:key name="kUnitID" match="Plan" use="@UnitID" />
<xsl:template match="/Root/Plans">
<html>
<head>
<title>test grouping</title>
</head>
<body>
<ol>
<xsl:for-each select="./Plan[generate-id(.) =
generate-id( key( 'kAreaID', @AreaID )[1] )]" >
<xsl:sort order="ascending" select="./@AreaID" />
<xsl:variable name="curArea" select="@AreaID"/>
<li>
Area <xsl:value-of select="$curArea"/>:
<ol>
<xsl:for-each select="ancestor::Root/Plans/Plan[@AreaID = $curArea]">
<xsl:variable name="curUnit" select="@UnitID"/>
<li>
Unit <xsl:value-of select="$curUnit"/>:
<ol>
<xsl:for-each select="ancestor::Root/Plans/Plan[@AreaID = $curArea and @UnitID = $curUnit]/Part">
<li>
Part <xsl:value-of select="concat(@ID, ' (', @Name, ')')"/>
</li>
</xsl:for-each>
</ol>
</li>
</xsl:for-each>
</ol>
</li>
</xsl:for-each>
</ol>
</body>
</html>
</xsl:template>
</xsl:stylesheet>