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

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

问题描述

好的,我知道已经询问并回答了关于此的变体;我已经读了一整天,但我仍然被困住了.所以,这里是:

我需要从一些 XML 中创建一个 HTML 摘要列表.

鉴于此 XML:

<计划><Plan AreaID="1" UnitID="83"><Part ID="9122" Name="foo"/><Part ID="9126" Name="bar"/></计划><Plan AreaID="1" UnitID="86"><Part ID="8650" Name="baz"/></计划><Plan AreaID="2" UnitID="26"><Part ID="215" Name="quux"/></计划><Plan AreaID="1" UnitID="95"><Part ID="7350" Name="meh"/></计划></计划></Root>

我需要发射:

    <li>区域1:<ol><!-- 区域 1 中的单位--><li>单元83:<ol><li>第 9122 部分 (foo)</li><li>第 9126 部分 (bar)</li></ol><li>单元86:<ol><li>第 8650 部分 (baz)</li></ol><li>单元95:<ol><li>第7350部分(meh)</li></ol></ol><!--/区域 1 中的单位--><li>区域2:<ol><!-- 区域 2 中的单位--><li>单元26:<ol><li>第 215 部分(quux)</li></ol></ol><!--/区域 2 中的单位--></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>测试分组</title></head><身体><ol><xsl:for-each select="./Plan[generate-id(.) =生成 id( key( 'kAreaID', @AreaID )[1] )]"><xsl:sort order="升序" select="./@AreaID"/><li>区域 <xsl:value-of select="@AreaID"/>:<ol><xsl:for-each select="key('kUnitID', @UnitID)"><li>Unit <xsl:value-of select="@UnitID"/>:<ol><li>(零件放在这里...)</li></ol></xsl:for-each></ol></xsl:for-each></ol></xsl:模板></xsl:stylesheet>

非常感谢任何帮助!

解决方案

这里是您正在寻找的 Muenchian 分组解决方案.

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

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

<xsl:key name="kPlanByAreaAndUnit" match="Plan"use="concat(@AreaID, ',', @UnitID)"/><xsl:template match="/"><xsl:apply-templates select="Root/Plans"/></xsl:模板><!-- 主模板--><xsl:template match="计划"><ol><!-- group by '{@AreaID}'(注意模板模式!)--><xsl:apply-templates mode="area-group" select="计划[生成 ID()=生成 ID(键('kPlanByArea',@AreaID)[1])]><xsl:sort select="@AreaID" data-type="number"/></xsl:apply-templates></ol></xsl:模板><!-- 输出每个{@AreaID}"组的模板--><xsl:template match="Plan" mode="area-group"><li><xsl:value-of select="concat('Area ', @AreaID)"/><ol><!-- 按'{@AreaID},{@UnitID}'分组--><xsl:apply-templates mode="unit-group" select="键('kPlanByArea',@AreaID)[生成 ID()=生成 ID(key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1])]><xsl:sort select="@UnitID" data-type="number"/></xsl:apply-templates></ol></xsl:模板><!-- 输出每个'{@AreaID},{@UnitID}'组的模板--><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></xsl:模板><!-- 将 Parts 输出到列表中的模板--><xsl:template match="Part"><li><xsl:value-of select="concat('Part ', @ID, '(', @Name ,')')"/></xsl:模板></xsl:stylesheet>

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

<Part ID="8651" Name="zzz"/></计划>

这是输出:

    <li>区域 1<ol><li>83单元<ol><li>第 9122 部分 (foo)</li><li>第 9126 部分 (bar)</li></ol><li>86单元<ol><li>第 8650 部分 (baz)</li><li>第 8651 部分 (zzz)</li></ol><li>95单元<ol><li>第7350部分(meh)</li></ol></ol><li>区域2<ol><li>单元26<ol><li>第 215 部分(quux)</li></ol></ol></ol>

<小时>

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

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

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

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

var kPlanByAreaAndUnit = {"1,83": ['array of all <Plan>具有@AreaID="1" 和@UnitID="83"'] 的节点,"1,86": ['array of all <Plan>具有@AreaID="1" 和@UnitID="86"'] 的节点,/* ... */"1,95": ['array of all <Plan>具有 @AreaID="1" 和 @UnitID="95"'] 的节点};

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

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

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

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

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

key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1]<!-- 具有 <Part> 的节点仅限儿童... -->key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[部分]

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

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

等等.这也意味着我们可以将它用作 的选择"表达式,并且我们可以将它用作分组的基础.这将我们引向上述样式表的核心(如果您已经了解了这个样式表,那么您也已经理解了解决方案的其余部分):

key('kPlanByArea', @AreaID)[生成 ID()=生成 ID(key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1])]

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

//结果会是一个节点集,所以我们准备一个数组var selectedNodes = [];//"key('kPlanByArea', @AreaID)"var nodeSet = kPlanByArea[this.AreaID];//"[...]" - [] 实际上触发了一个循环//集合中所有节点的谓词表达式,所以我们这样做:for (var i = 0; i < nodeSet.length; i++) {//使用当前节点进行任何计算var c = nodeSet[i];如果 (//如果当前节点 === kPlanByAreaAndUnit 中的 *first* 节点...生成 ID(c)==generateId(kPlanByAreaAndUnit[c.AreaID + ',' + c.UnitID][0])){//...将其包含在结果选择中selectedNodes.push(c)}}

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

将模板应用于此节点集会导致每个组合仅出现一次.我的 然后再次检索完整列表以实现每个组的完整输出.

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

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:

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

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

I need to emit:

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

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>

Any help is greatly appreciated!

解决方案

Here is the Muenchian grouping solution you are looking for.

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.

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>

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

<Plan AreaID="1" UnitID="86">
  <Part ID="8651" Name="zzz" />
</Plan>

And here is the output:

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


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

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)" />

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"']
};

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

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

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

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

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]

or use it as a base for XPath navigation:

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

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

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

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.

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.

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

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

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