これがあなたが探しているミュンヒアンのグループ化ソリューションです。
提供した元のXMLから、AreaIDによるグループ化で十分だと思いましたが、UnitIDによる2番目のグループ化も必要であることがわかりました。
これが私の修正したXSLT1.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キーに苦労しているようですので、ここで説明を試みます。
An<xsl:key>
は、多くのプログラミング言語で知られている連想配列(マップ、ハッシュ、あなたがそれを呼ぶものは何でも)と完全に同等です。これ:
<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、UnitID」の組み合わせを持つそれぞれの最初のノードであるノードのみが選択されます。事実上、それらのノードは「AreaID、UnitID」の組み合わせでグループ化されています。
このノードセットにテンプレートを適用すると、すべての組み合わせが1回だけ表示されます。次に、 My<xsl:template match="Plan" mode="unit-group">
は完全なリストを再度取得して、各グループの完全な出力を実現します。
概念を説明するためにJavaScriptを使用することが有益なアイデアであったことを願っています。