属性上的XSLT 3级分组

好吧,我知道这个变化已经被问到和回答了。 我一整天都在读它们,但是我还是卡住了。 所以,在这里:

我需要从一些XML在HTML中创build一个摘要列表。

鉴于这个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.但我不能得到单位在区域的序列 – 我得到没有输出,或重复相同的值。 我甚至还没有到部分级别:-(

我一直在做这样的样式表:

 <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> 

任何帮助是极大的赞赏!

这里是您正在寻找的Muenchian分组解决scheme。

从您提供的原始XML开始,我认为按照AreaID分组就足够了,但事实certificate,还需要由UnitID进行第二次分组。

这是我修改后的XSLT 1.0解决scheme。 这并不比原来的解决scheme复杂得多。

 <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缺less它,我添加了一个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() 。 所以,这个XPathexpression式:

 key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID)) 

是(在JavaScript中,再次)的逻辑等价物:

 kPlanByAreaAndUnit[this.AreaID + ',' + this.UnitID]; 

返回与给定的键string匹配的所有节点的数组(一个节点集合,更准确的说,键字总是一个string)。 这个节点集可以像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>的“select”expression式,我们可以用它作为分组的基础。 这导致我们到了上面样式表的核心(如果你已经把头围绕在这个样式表上,那么你已经理解了解决scheme的其余部分):

 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) } } 

expression式完成之后,只有那些具有给定的“AreaID,UnitID”组合的第一个节点才被select – 我们已经有效地将它们分组到了它们的“AreaID,UnitID”组合中。

将模板应用于此节点集使每个组合仅出现一次。 我的<xsl:template match="Plan" mode="unit-group">然后再次检索完整列表以实现每个组的完整输出。

我希望使用JavaScript来解释这个概念是一个有用的想法。

我不认为你需要使用kUnitID键。 而是replace下面的行…

 <xsl:for-each select="key( 'kUnitID', @UnitID )"> 

..用这行代替,它应该遍历所有匹配当前AreaID的部分

 <xsl:for-each select="key( 'kAreaID', @AreaID )"> 

在这个循环中,对于你的(Parts go here …)代码,你可以简单地遍历这些部分

 <xsl:for-each select="Part"> <li>Part (<xsl:value-of select="@ID" />)</li> </xsl:for-each> 

好吧,我暂时放弃了键和Muenchian组合。 我几乎不明白这一点,而且对它没有产生预期的结果。 我理解recursion,所以我走了这个recursion的方法,产生所需的输出。 我发现它在http://www.biglist.com/lists/xsl-list/archives/200412/msg00865.html

讨论线索警告说,性能受到大量input与Muenchian方法的影响,下面的解决scheme是冗长和重复的(我可以重构它以使它更小更难以理解;-)但是1)它实际上适用于我, 2)对于我现在的问题,input集合相当小,不超过十几个底层部分节点。

 <?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> 

这是做你想要的,但与recursion,而不是分组。 对不起,我还在学习如何使用分组:

 <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>