XSLT属性的3级分组

Val*_*Val 4 xslt grouping

好的,我知道已经提出并回答了对此的各种变化; 我整天都在读它们,但我仍然被卡住了.所以,这里是:

我需要从一些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>
Run Code Online (Sandbox Code Playgroud)

我需要发出:

<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>
Run Code Online (Sandbox Code Playgroud)

我有外部分组工作 - 我得到区域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>
Run Code Online (Sandbox Code Playgroud)

任何帮助是极大的赞赏!

Tom*_*lak 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>
Run Code Online (Sandbox Code Playgroud)

由于您缺少XML,我添加了一个UnitID来分组:

<Plan AreaID="1" UnitID="86">
  <Part ID="8651" Name="zzz" />
</Plan>
Run Code Online (Sandbox Code Playgroud)

这是输出:

<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>
Run Code Online (Sandbox Code Playgroud)

由于您似乎很难使用XSL密钥,因此我尝试解释:

An <xsl:key>绝对等同于许多编程语言已知的关联数组(map,hash,无论你怎么称呼它).这个:

<xsl:key name="kPlanByAreaAndUnit" match="Plan" 
         use="concat(@AreaID, ',', @UnitID)" />
Run Code Online (Sandbox Code Playgroud)

生成一个可以用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"']
};
Run Code Online (Sandbox Code Playgroud)

调用访问数据结构的函数key().所以,这个XPath表达式:

key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))
Run Code Online (Sandbox Code Playgroud)

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

kPlanByAreaAndUnit[this.AreaID + ',' + this.UnitID];
Run Code Online (Sandbox Code Playgroud)

返回与给定键字符串匹配的所有节点的数组(更正确的节点集)(键始终为字符串).此节点集可以像在XSLT中的任何其他节点集一样使用,即与通过"传统"XPath检索的节点集类似.这意味着您可以对其应用条件(谓词):

<!-- first node only... -->
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1]

<!-- nodes that have <Part> children only... -->
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[Part]
Run Code Online (Sandbox Code Playgroud)

或者将其用作XPath导航的基础:

<!-- the actual <Part> children of matched nodes... -->
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))/Part
Run Code Online (Sandbox Code Playgroud)

等等.这也意味着我们可以将它用作"选择"表达式<xsl:apply-templates>,我们可以将其用作分组的基础.这导致我们进入上述样式表的核心(如果你已经围绕这个样式表,你也理解了其余的解决方案):

key('kPlanByArea', @AreaID)[
  generate-id()
  =
  generate-id(
    key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1]
  )
]
Run Code Online (Sandbox Code Playgroud)

再次在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)
   }
}
Run Code Online (Sandbox Code Playgroud)

表达式完成后,只选择那些具有给定"AreaID,UnitID"组合的第一个节点 - 实际上我们将它们分组在它们的"AreaID,UnitID"组合上.

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

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

  • 同意,xsl:key的最佳解释我在研究的日子里见过!所有其他的都是挥手('只写这个,然后魔术发生!'),或者是学术界为规范委员会撰写的过于冗长的规范.你的JavaScript类比很棒,我终于可以阅读key()语句并理解它们.我已经投票了,但还没有enuf代表 - 但我会把它标记为答案,即使我坚持我写的东西(通过UAT,已经在生产中:-).谢谢您的帮助! (3认同)