代码之家  ›  专栏  ›  技术社区  ›  Val

XSLT属性三级分组

  •  4
  • Val  · 技术社区  · 17 年前

    我需要从一些XML创建HTML格式的摘要列表。

    <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的顶级列表元素。但我无法获得Area中的Units序列——我要么没有得到输出,要么重复相同的值。我甚至还没有到零件级别:-(

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

    4 回复  |  直到 17 年前
        1
  •  18
  •   Tomalak    14 年前

    根据您提供的原始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:key>

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

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

    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]
    

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

    // 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来解释这个概念是一个有帮助的想法。

        2
  •  1
  •   Tim C    17 年前

    我认为你不需要使用 kUnitID 钥匙在所有。请替换以下行。..

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

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

    在这个循环中,为您 代码,然后您可以简单地循环这些部分

    <xsl:for-each select="Part">
       <li>Part (<xsl:value-of select="@ID" />)</li>
    </xsl:for-each>
    
        3
  •  1
  •   Val    17 年前

    好吧,我暂时放弃了钥匙和Muenchian分组。我几乎不理解它,而且不断地破解它并没有产生预期的结果。我理解递归,所以我采用了这种递归方法来产生所需的输出。我发现它在 http://www.biglist.com/lists/xsl-list/archives/200412/msg00865.html

    作品 对我来说,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>
    
        4
  •  0
  •   Jay    17 年前

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