XSLT属性的三级分组 [英] XSLT 3-level grouping on attributes

查看:83
本文介绍了XSLT属性的三级分组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好的,我知道已经提出并回答了各种变化;我整天都在阅读它们,但我仍然陷于困境.因此,这里是:

OK, I KNOW that variations on this have been asked and answered; I've been reading them all day, but I'm still stuck. So, here goes:

我需要从XML中创建HTML的摘要列表.

I need to create a summary list in HTML from some XML.

给出此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级别:-(

I have the outer grouping working -- I get top-level list elements for Area 1 and 2. But I can't get the sequences of Units in the Areas -- I either get no output, or repeating the same value. I haven't even got down to the Part level :-(

我一直在处理这样的样式表:

I've been working on a stylesheet like this:

<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分组解决方案.

Here is the Muenchian grouping solution you are looking for.

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

Going from the original XML you provided, I thought grouping by AreaID would be enough, but it turns out that a second grouping by UnitID is also needed.

这是我修改后的XSLT 1.0解决方案.它并没有比原始解决方案复杂得多:

Here is my modified XSLT 1.0 solution. It's not a lot more complex than the original solution:

<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进行分组:

Since your XML is missing it, I added a UnitID to group on:

<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键,因此在此尝试做一个解释:


Since you seem to have a hard time with the XSL key, here my attempt of an explanation:

<xsl:key>绝对等同于许多编程语言已知的关联数组(映射,哈希,无论您叫什么).这个:

An <xsl:key> is absolutely equivalent to the associative array (map, hash, whatever you call it) known to many programming languages. This:

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

生成可以用JavaScript表示的数据结构,如下所示:

generates a data structure that could be expressed in JavaScript like this:

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表达式:

The function to access the data structure is called key(). So, this XPath expression:

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

在逻辑上等效(再次在JavaScript中):

is the logical equivalent of (in JavaScript, again):

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

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

returning an array (a node-set, more correctly) of all nodes matching the given key string (the key is always a string). This node-set can be used like any other node-set in XSLT, i.e. like the ones you retrieve through "traditional" XPath. This means you can apply conditions (predicates) to it:

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

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

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

or use it as a base for XPath navigation:

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

,依此类推.这也意味着我们可以将其用作<xsl:apply-templates>的选择"表达式,并且可以将其用作分组的基础.这就引出了上述样式表的核心(如果您对此一无所知,那么您也已经了解了解决方案的其余部分):

and so on. This also means we can use it as a "select" expression for <xsl:apply-templates>, and we can use it as a base for grouping. Which leads us to the core of the above stylesheet (if you have wrapped your head around this one, you've understood the rest of the solution as well):

key('kPlanByArea', @AreaID)[
  generate-id()
  =
  generate-id(
    key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1]
  )
]

再次在JavaScript中,它可以表示为:

In JavaScript again, this could be expressed as:

// 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"组合上.

After the expression is done, only those nodes are selected that are the respective first ones with a given "AreaID, UnitID" combination - effectively we have grouped them on their "AreaID, UnitID" combination.

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

Applying a template to this node-set causes every combination to appear only once. My <xsl:template match="Plan" mode="unit-group"> then retrieves the full list again to achieve complete output for each group.

我希望使用JavaScript来解释这个概念是有帮助的.

I hope the use of JavaScript to explain the concept was a helpful idea.

这篇关于XSLT属性的三级分组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆