2009-06-04 66 views
4

好吧,我知道这个变化已被问及回答;我一整天都在看他们,但我仍然陷入困境。所以,这里有:属性上的XSLT 3级别分组

我需要从HTML中创建一个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,但我顶层列表元素无法获得区域中的单位序列 - 我得不到输出或重复相同的值。我还没有拿到下降到部件级:-(

我一直工作在一个样式像这样:

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

任何帮助是极大的赞赏

+0

谢谢,这让我更加接近。我还是有一点麻烦 - 第二级元素是重复的,而不是分组,所以我得到 区1 单元83 部分9122 单元83 部分9126 单位86 部分8650 代替 区1个 单元83 部分9122 部分9126 单元86 部分8650 但它更接近了很多比我! – Val 2009-06-04 17:58:13

+0

哎呀,我看到的评论没有格式相同的方式作为帖子:( – Val 2009-06-04 18:00:06

+0

不,评论格式为纯文本(但尖括号<>是允许的)。请检查我修改后的解决方案。:) – Tomalak 2009-06-09 10:24:41

回答

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> 

由于您的XML缺少它,我添加了一个单元ID到组:

<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()。所以,这XPath表达式:

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] 

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

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

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

表达完成后,只有那些节点被选择的是与给定的“areaID表示,单元ID”组合各自的第一的 - 我们实际上已经编组他们在他们的“areaID表示,单元ID”的组合。

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

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

0

这做什么你想要的,但与递归,不分组对不起我仍然在学习如何使用分组太:

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

我不认为你需要使用kUnitID关键在所有。相反,替换下面的行...

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

..with此行,而不是,它应该遍历所有部分匹配当前areaID表示

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

而且在这个循环中,为您(零件去这里...)码,所以可以只在部分

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

非常好!但是,我得到了类似的结果上面 - 第2级元素是重复的,而不是分组,所以我得到 区1 单元83 部分9122 单元83 部分9126 单位86 部分8650 代替 区1个 单元83 部分9122 部分9126 单位86 部分8650 我会继续在它黑客。谢谢您的帮助! – Val 2009-06-04 17:59:22

1

井环,我放弃了对键和Muenchian暂时分组。我几乎无法理解它,并且对它没有产生期望的结果。我明白递归,所以我用这种递归方法来产生所需的输出。我发现它在 http://www.biglist.com/lists/xsl-list/archives/200412/msg00865.html

话题警告说,性能上大的输入遭受主场迎战Muenchian做法,下面的解决方案是冗长和重复(我可以prolly重构它,使其体积更小,难于理解; - )但1)它实际上工程对我来说,和2)对于我现在的问题,输入集非常小,不超过十几个底层的部分节点。

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